web-dev-qa-db-fra.com

Essayez monad en Java 8

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.

13
Jiri Kremser

Le projet "Better-Java-Monads" sur GitHub a un Try monad for Java 8 ici

11
lbalazscs

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;
 }
13
John McClean

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.

4
Richard Polton

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");
    }
});
3
Misha

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.

1
ncaralicea

@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

Implémentations de 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);
}
1
Luke Worth