Existe-t-il un support intégré pour monad qui traite du traitement des exceptions? Quelque chose de semblable à Scala Essayez . Je demande parce que je n'aime pas les exceptions non contrôlées.
Le projet "Better-Java-Monads" sur GitHub a un Try monad for Java 8 ici .
Il existe au moins deux solutions généralement disponibles (par exemple sur Maven Central) - Vavr et Cyclops les deux ont des implémentations Try qui adoptent une approche légèrement différente.
Vavr's Try suit très étroitement Scala's Try. Toutes les exceptions "non fatales" générées lors de l'exécution de ses combinateurs seront interceptées.
Cyclops Try n'acceptera que les exceptions explicitement configurées (bien sûr, vous pouvez également tout capturer par défaut), et le mode de fonctionnement par défaut consiste à capturer uniquement lors de la méthode de remplissage initial. Le raisonnement derrière cela est que Try se comporte de manière quelque peu similaire à Optional - Optional n'encapsule pas de valeurs Null inattendues (bugs), mais uniquement aux endroits où nous nous attendons raisonnablement à ne pas avoir de valeur.
Voici un exemple Essayez avec les ressources de Cyclops
Try t2 = Try.catchExceptions(FileNotFoundException.class,IOException.class)
.init(()->PowerTuples.Tuple(new BufferedReader(new FileReader("file.txt")),new FileReader("hello")))
.tryWithResources(this::read2);
Et un autre exemple 'levant' une méthode existante (pouvant être divisée par zéro) pour prendre en charge la gestion des erreurs.
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.*;
import static com.aol.cyclops.lambda.api.AsAnyM.anyM;
import lombok.val;
val divide = Monads.liftM2(this::divide);
AnyM<Integer> result = divide.apply(anyM(Try.of(2, ArithmeticException.class)), anyM(Try.of(0)));
assertThat(result.<Try<Integer,ArithmeticException>>unwrapMonad().isFailure(),equalTo(true));
private Integer divide(Integer a, Integer b){
return a/b;
}
Tout d'abord, permettez-moi de m'excuser d'avoir répondu au lieu de commenter - apparemment, j'ai besoin de 50 points de réputation pour commenter ...
@ncaralicea, votre implémentation est similaire à la mienne, mais le problème que j’avais était de savoir comment réconcilier try ... catch in bind () et les lois sur l’identité. Plus précisément, return x >> = f est équivalent à f x . Quand bind () attrape l'exception, alors f x diffère car il est lancé.
De plus, le transformateur semble être a -> b au lieu de a -> M b . Ma version actuelle de bind (), même si je la trouve insatisfaisante, est
public <R> MException<R> bind(final Function<T, MException<R>> f) {
Validate.notNull(f);
if (value.isRight())
try {
return f.apply(value.right().get());
} catch (final Exception ex) {
return new MException<>(Either.<Exception, R>left(ex));
}
else
return new MException<>(Either.<Exception, R>left(value.left().get()));
}
où la valeur est un
Either<? extends Exception,T>
Le problème de la loi sur l’identité est qu’elle exige que la fonction f détecte des exceptions qui vont à l’encontre de l’objet de l’exercice.
Ce que je pense que vous pourriez réellement souhaiter, c’est le Functor et non la Monad C'est la fmap: (a-> b) -> f a -> f b fonction.
Si vous écrivez
@Override
public <R> MException<R> fmap(final Function<T, R> fn) {
Validate.notNull(fn);
if (value.isRight())
try {
return new MException<>(Either.<Exception, R>right(fn.apply(value.right().get())));
} catch (final Exception ex) {
return new MException<>(Either.<Exception, R>left(ex));
}
else
return new MException<>(Either.<Exception, R>left(value.left().get()));
}
dans ce cas, vous n'avez pas besoin d'écrire du code de gestion des exceptions explicite, d'implémenter de nouvelles interfaces ou de vous incommoder avec les lois de Monad.
Vous pouvez faire ce que vous voulez en utilisant (ab) en utilisant CompletableFuture
. S'il vous plaît ne faites pas cela dans aucune sorte de code de production.
CompletableFuture<Scanner> sc = CompletableFuture.completedFuture(
new Scanner(System.in));
CompletableFuture<Integer> divident = sc.thenApply(Scanner::nextInt);
CompletableFuture<Integer> divisor = sc.thenApply(Scanner::nextInt);
CompletableFuture<Integer> result = divident.thenCombine(divisor, (a,b) -> a/b);
result.whenComplete((val, ex) -> {
if (ex == null) {
System.out.printf("%s/%s = %s%n", divident.join(), divisor.join(), val);
} else {
System.out.println("Something went wrong");
}
});
Il existe ici une implémentation qui pourrait être utilisée comme modèle. Vous trouverez des informations complémentaires ici:
Java avec les calculs d'essais, d'échecs et de succès
Vous pouvez fondamentalement faire quelque chose comme ceci:
public class Test {
public static void main(String[] args) {
ITransformer < String > t0 = new ITransformer < String > () {@
Override
public String transform(String t) {
//return t + t;
throw new RuntimeException("some exception 1");
}
};
ITransformer < String > t1 = new ITransformer < String > () {@
Override
public String transform(String t) {
return "<" + t + ">";
//throw new RuntimeException("some exception 2");
}
};
ComputationlResult < String > res = ComputationalTry.initComputation("1").bind(t0).bind(t1).getResult();
System.out.println(res);
if (res.isSuccess()) {
System.out.println(res.getResult());
} else {
System.out.println(res.getError());
}
}
}
Et voici le code:
public class ComputationalTry < T > {
final private ComputationlResult < T > result;
static public < P > ComputationalTry < P > initComputation(P argument) {
return new ComputationalTry < P > (argument);
}
private ComputationalTry(T param) {
this.result = new ComputationalSuccess < T > (param);
}
private ComputationalTry(ComputationlResult < T > result) {
this.result = result;
}
private ComputationlResult < T > applyTransformer(T t, ITransformer < T > transformer) {
try {
return new ComputationalSuccess < T > (transformer.transform(t));
} catch (Exception throwable) {
return new ComputationalFailure < T, Exception > (throwable);
}
}
public ComputationalTry < T > bind(ITransformer < T > transformer) {
if (result.isSuccess()) {
ComputationlResult < T > resultAfterTransf = this.applyTransformer(result.getResult(), transformer);
return new ComputationalTry < T > (resultAfterTransf);
} else {
return new ComputationalTry < T > (result);
}
}
public ComputationlResult < T > getResult() {
return this.result;
}
}
public class ComputationalFailure < T, E extends Throwable > implements ComputationlResult < T > {
public ComputationalFailure(E exception) {
this.exception = exception;
}
final private E exception;
@Override
public T getResult() {
return null;
}
@Override
public E getError() {
return exception;
}
@Override
public boolean isSuccess() {
return false;
}
}
public class ComputationalSuccess < T > implements ComputationlResult < T > {
public ComputationalSuccess(T result) {
this.result = result;
}
final private T result;
@Override
public T getResult() {
return result;
}
@Override
public Throwable getError() {
return null;
}
@Override
public boolean isSuccess() {
return true;
}
}
public interface ComputationlResult < T > {
T getResult();
< E extends Throwable > E getError();
boolean isSuccess();
}
public interface ITransformer < T > {
public T transform(T t);
}
public class Test {
public static void main(String[] args) {
ITransformer < String > t0 = new ITransformer < String > () {@
Override
public String transform(String t) {
//return t + t;
throw new RuntimeException("some exception 1");
}
};
ITransformer < String > t1 = new ITransformer < String > () {@
Override
public String transform(String t) {
return "<" + t + ">";
//throw new RuntimeException("some exception 2");
}
};
ComputationlResult < String > res = ComputationalTry.initComputation("1").bind(t0).bind(t1).getResult();
System.out.println(res);
if (res.isSuccess()) {
System.out.println(res.getResult());
} else {
System.out.println(res.getError());
}
}
}
J'espère que cela pourrait obscurcir un peu la lumière.
@Misha est sur quelque chose. Évidemment, vous ne feriez pas cette chose exactement dans le code réel, mais CompletableFuture
fournit des monades de style Haskell comme ceci:
return
est mappé sur CompletableFuture.completedFuture
>=
correspond à thenCompose
Ainsi, vous pourriez réécrire l'exemple de @ Misha comme ceci:
CompletableFuture.completedFuture(new Scanner(System.in)).thenCompose(scanner ->
CompletableFuture.completedFuture(scanner.nextInt()).thenCompose(divident ->
CompletableFuture.completedFuture(scanner.nextInt()).thenCompose(divisor ->
CompletableFuture.completedFuture(divident / divisor).thenCompose(val -> {
System.out.printf("%s/%s = %s%n", divident, divisor, val);
return null;
}))));
qui correspond au Haskell-ish:
(return (newScanner SystemIn)) >>= \scanner ->
(return (nextInt scanner)) >>= \divident ->
(return (nextInt scanner)) >>= \divisor ->
(return (divident / divisor)) >>= \val -> do
SystemOutPrintf "%s/%s = %s%n" divident divisor val
return Null
ou avec la syntaxe do
do
scanner <- return (newScanner SystemIn)
divident <- return (nextInt scanner)
divisor <- return (nextInt scanner)
val <- return (divident / divisor)
do
SystemOutPrintf "%s/%s = %s%n" divident divisor val
return Null
fmap
et join
Je me suis un peu emporté. Il s'agit des noms fmap
et join
standard implémentés en termes de CompletableFuture
:
<T, U> CompletableFuture<U> fmap(Function<T, U> f, CompletableFuture<T> m) {
return m.thenCompose(x -> CompletableFuture.completedFuture(f.apply(x)));
}
<T> CompletableFuture<T> join(CompletableFuture<CompletableFuture<T>> n) {
return n.thenCompose(x -> x);
}