webentwicklung-frage-antwort-db.com.de

Versuchen Sie Monade in Java 8

Gibt es eine integrierte Unterstützung für Monad, die sich mit der Behandlung von Ausnahmen befasst? Etwas Ähnliches zu Scalas Try . Ich frage, weil ich ungeprüfte Ausnahmen nicht mag.

13
Jiri Kremser

Das "better-Java-monads" -Projekt auf GitHub enthält eine Try-Monade für Java 8 hier

11
lbalazscs

Es gibt mindestens zwei allgemein verfügbare (z. B. auf Maven Central) - Vavr und Cyclops beide haben Try-Implementierungen, die einen etwas anderen Ansatz verwenden.

Vavrs Versuch folgt Scalas Versuch sehr genau. Es fängt alle "nicht tödlichen" Ausnahmen auf, die während der Ausführung seiner Kombinatoren ausgelöst werden.

Cyclops Try fängt nur explizit konfigurierte Ausnahmen ab (natürlich können Sie es standardmäßig auch alles abfangen lassen), und der Standardbetriebsmodus ist nur während der ursprünglichen Populationsmethode abzufangen. Der Grund dafür ist, dass Try sich auf eine ähnliche Weise verhält wie Optional - Optional kapselt unerwartete Nullwerte (d. H. Fehler) nicht ein, nur an Stellen, an denen wir vernünftigerweise keinen Wert erwarten.

Hier ist ein Beispiel: Try With Resources von Cyclops 

 Try t2 = Try.catchExceptions(FileNotFoundException.class,IOException.class)
               .init(()->PowerTuples.Tuple(new BufferedReader(new FileReader("file.txt")),new FileReader("hello")))
               .tryWithResources(this::read2);

Und ein anderes Beispiel "heben" eine vorhandene Methode (die durch Null geteilt werden kann), um die Fehlerbehandlung zu unterstützen.

    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

Zunächst möchte ich mich für die Antwort entschuldigen, anstatt zu kommentieren - anscheinend brauche ich 50 Ruf, um zu kommentieren ...

@ncaralicea Deine Implementierung ähnelt meiner eigenen, aber das Problem, das ich hatte, war, wie man try ... catch in bind () mit den Identitätsgesetzen in Einklang bringt. Speziell return x >> = f ist äquivalent zu f x . Wenn bind () die Ausnahme abfängt, unterscheidet sich f x , weil es ausgelöst wird.

Außerdem scheint der ITransformer a -> b anstelle von a -> M b zu sein. Meine aktuelle Version von bind () ist unbefriedigend, obwohl ich sie finde

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()));
}

wo Wert ein ist 

Either<? extends Exception,T>

Das Problem des Identitätsgesetzes besteht darin, dass die Funktion f Ausnahmen fangen muss, die den gesamten Zweck der Übung zunichte machen.

Ich denke, Sie möchten eigentlich den Functor und nicht die Monade. Das ist die fmap: (a-> b) -> fa -> fb-Funktion.

Wenn du schreibst 

@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()));
}

dann müssen Sie keinen expliziten Ausnahmebehandlungscode schreiben, neue Schnittstellen implementieren oder sich mit den Monad-Gesetzen beschäftigen.

4
Richard Polton

Sie können machen, was Sie wollen, indem Sie (ab) mit CompletableFuture. Bitte machen Sie dies nicht in irgendeiner Art von Produktionscode. 

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

Hier gibt es eine Implementierung, die als Modell verwendet werden kann. Weitere Informationen finden Sie hier:

Java mit Try, Failure und Success-basierten Berechnungen

Sie können im Grunde so etwas tun:

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());
    }
  }
}

Und hier ist der 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());
    }
  }
}

Ich hoffe, dass dies etwas Licht verschattet.

1
ncaralicea

@Misha steht auf etwas. Offensichtlich würden Sie dies nicht in echtem Code tun, aber CompletableFuture liefert Monde im Haskell-Stil wie folgt:

  • return ordnet sich CompletableFuture.completedFuture zu
  • >= bildet thenCompose ab

Du könntest also @ Mishas Beispiel so umschreiben:

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;
}))));

was dem Haskell-ish zuordnet:

(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

oder mit do-Syntax

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

Implementierungen von fmap und join

Ich wurde etwas mitgerissen. Dies sind die Standards fmap und join, die im Hinblick auf CompletableFuture implementiert werden:

<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