例外処理を扱うモナドの組み込みサポートはありますか? Scalaの Try に似たもの。未チェックの例外が気に入らないので、私は尋ねています。
GitHubの「better-Java-monads」プロジェクトには、Java 8 here 。
一般的に利用可能な少なくとも2つがあります(例:Maven Central)- Vavr と Cyclops には、わずかに異なるアプローチを取る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;
}
まず、コメントするのではなく、回答してくれたことをお詫び申し上げます。コメントするには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()));
}
そうすれば、明示的な例外処理コードを記述したり、新しいインターフェースを実装したり、モナドの法則で混乱させたりする必要はありません。
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");
}
});
ここに、モデルとして使用できる実装があります。詳細については、こちらをご覧ください。
あなたは基本的にこのようなことをすることができます:
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());
}
}
}
これが光を遮るのではないかと思います。
@Mishaは何かに取り組んでいます。もちろん、実際のコードではこれを正確に行うことはありませんが、CompletableFuture
は次のようなHaskellスタイルのモナドを提供します。
return
はCompletableFuture.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);
}