Por sugestão da @on, aqui está uma resposta mais monádica. É uma versão bastante simplificada, na qual você deve aceitar algumas suposições:
a função "unit" ou "return" é o construtor da classe
a operação "bind" acontece no momento da compilação, portanto é oculta da invocação
as funções "action" também são vinculadas à classe em tempo de compilação
embora a classe seja genérica e envolva qualquer classe arbitrária E, acho que é realmente um exagero nesse caso. Mas deixei assim como um exemplo do que você poderia fazer.
Com essas considerações, a mônada se traduz em uma classe de wrapper fluente (embora você esteja perdendo muita flexibilidade que obteria em uma linguagem puramente funcional):
public class RepositoryLookup<E> {
private String source;
private E answer;
private Exception exception;
public RepositoryLookup<E>(String source) {
this.source = source;
}
public RepositoryLookup<E> fetchElement() {
if (answer != null) return this;
if (! exception instanceOf NotFoundException) return this;
try {
answer = lookup(source);
}
catch (Exception e) {
exception = e;
}
return this;
}
public RepositoryLookup<E> orFetchSimilarElement() {
if (answer != null) return this;
if (! exception instanceOf NotFoundException) return this;
try {
answer = lookupVariation(source);
}
catch (Exception e) {
exception = e;
}
return this;
}
public RepositoryLookup<E> orFetchParentElement() {
if (answer != null) return this;
if (! exception instanceOf NotFoundException) return this;
try {
answer = lookupParent(source);
}
catch (Exception e) {
exception = e;
}
return this;
}
public boolean failed() {
return exception != null;
}
public Exception getException() {
return exception;
}
public E getAnswer() {
// better to check failed() explicitly ;)
if (this.exception != null) {
throw new IllegalArgumentException(exception);
}
// TODO: add a null check here?
return answer;
}
}
(isso não será compilado ... alguns detalhes são deixados inacabados para manter a amostra pequena)
E a invocação ficaria assim:
Repository<String> repository = new Repository<String>(x);
repository.fetchElement().orFetchParentElement().orFetchSimilarElement();
if (repository.failed()) {
throw new IllegalArgumentException(repository.getException());
}
System.err.println("Got " + repository.getAnswer());
Observe que você tem a flexibilidade de compor as operações de "busca" como desejar. Parará quando receber uma resposta ou uma exceção diferente da não encontrada.
Eu fiz isso muito rápido; não está certo, mas espero que transmita a ideia
NotFoundException
algo realmente excepcional?