web-dev-qa-db-ja.com

Java 8でモナドを試してください

例外処理を扱うモナドの組み込みサポートはありますか? Scalaの Try に似たもの。未チェックの例外が気に入らないので、私は尋ねています。

15
Jiri Kremser

GitHubの「better-Java-monads」プロジェクトには、Java 8 here

13
lbalazscs

一般的に利用可能な少なくとも2つがあります(例:Maven Central)- VavrCyclops には、わずかに異なるアプローチを取るTry実装があります。

VavrのTry は、ScalaのTryに非常に厳密に従っています。コンビネータの実行中にスローされたすべての「致命的でない」例外をキャッチします。

Cyclops Try は、明示的に構成された例外のみをキャッチします(もちろん、デフォルトでは、すべてをキャッチすることもできます)。デフォルトの動作モードは、初期のポピュレーションメソッド中にのみキャッチします。これの背後にある理由は、TryがOptionalと多少似た方法で動作するようにするためです-Optionalは予期しないNull値(バグなど)をカプセル化せず、値がないと合理的に予想される場所のみをカプセル化します。

サイクロプスのリソースを使ってみてください。

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

また、エラー処理をサポートするために既存のメソッド(ゼロで割る可能性がある)を「持ち上げる」別の例。

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

まず、コメントするのではなく、回答してくれたことをお詫び申し上げます。コメントするには50件の評判が必要なようです...

@ncaraliceaあなたの実装は私のものと似ていますが、私が持っていた問題は、アイデンティティの法則を使ってbind()でtry ... catchを調整する方法でした。具体的にはreturn x >> = fはf xと同等です。 bind()が例外をキャッチすると、スローするため、f xは異なります。

さらに、ITransformerはa-> M bではなくa-> bのように見えます。 bind()の私の現在のバージョンは、私が見つけたとしても不十分です。

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

ここで、値は

Either<? extends Exception,T>

アイデンティティ法の問題は、エクササイズの全体的な目的に反する例外をキャッチするために関数fを必要とすることです。

モナドではなく、ファンクターが実際に欲しいと思うかもしれません。それはfmap:(a-> b)-> f a-> f b関数です。

あなたが書くなら

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

そうすれば、明示的な例外処理コードを記述したり、新しいインターフェースを実装したり、モナドの法則で混乱させたりする必要はありません。

4
Richard Polton

CompletableFutureを(ab)使用することで、必要な操作を実行できます。いかなる量産コードでもこれを行わないでください。

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

ここに、モデルとして使用できる実装があります。詳細については、こちらをご覧ください。

試行、失敗、成功に基づく計算を使用したJava

あなたは基本的にこのようなことをすることができます:

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

そしてここにコードがあります:

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

これが光を遮るのではないかと思います。

1
ncaralicea

@Mishaは何かに取り組んでいます。もちろん、実際のコードではこれを正確に行うことはありませんが、CompletableFutureは次のようなHaskellスタイルのモナドを提供します。

  • returnCompletableFuture.completedFutureにマップされます
  • >=thenComposeにマップされます

したがって、@ Mishaの例を次のように書き換えることができます。

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

これはHaskell風にマップされます:

(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

または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およびjoinの実装

私は少し夢中になりました。これらは、fmapに関して実装された標準のjoinおよび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