【发布时间】:2015-01-05 20:57:22
【问题描述】:
是否有对处理异常处理的 monad 的内置支持?类似于 Scala 的Try。我问是因为我不喜欢未经检查的异常。
【问题讨论】:
是否有对处理异常处理的 monad 的内置支持?类似于 Scala 的Try。我问是因为我不喜欢未经检查的异常。
【问题讨论】:
至少有两个普遍可用的(例如在 Maven Central 上) - Vavr 和 Cyclops 都有 Try 实现,采用的方法略有不同。
Vavr's Try 非常接近 Scala 的 Try。它将捕获在其组合器执行期间抛出的所有“非致命”异常。
Cyclops Try 只会捕获显式配置的异常(当然你也可以,默认情况下,也让它捕获一切),并且默认的操作模式是仅在初始填充方法期间捕获。这背后的原因是 Try 的行为方式与 Optional 有点相似 - Optional 不会封装意外的 Null 值(即错误),只是我们合理预期没有值的地方。
这是一个使用 Cyclops 资源的示例
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;
}
【讨论】:
GitHub 上的“better-java-monads”项目有一个 Try monad for Java 8 here。
【讨论】:
finally 子句来指定。这些单子库是否有类似的东西可以达到相同的效果?
首先,让我为回答而不是评论而道歉 - 显然我需要 50 声望才能发表评论...
@ncaralicea 您的实现与我自己的类似,但我遇到的问题是如何将 bind() 中的 try ... catch 与身份法则相协调。具体来说,return x >>= f 等价于 f x。当 bind() 捕获异常时,f x 会有所不同,因为它会抛出异常。
此外,ITransformer 似乎是 a -> b 而不是 a -> M 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 来捕获异常,这违背了整个练习的目的。
我认为你可能真正想要的是 Functor 而不是 Monad。那就是 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()));
}
那么您就不需要编写显式的异常处理代码、实现新接口或搞乱 Monad 法则。
【讨论】:
您可以通过 (ab) 使用 CompletableFuture 来做您想做的事。请不要在任何类型的生产代码中这样做。
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");
}
});
【讨论】:
这里有一个可以用作模型的实现。 更多信息可以在这里找到:
Java with Try, Failure, and Success based computations
你基本上可以这样做:
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 风格的 monad:
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-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
或使用 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 的实现
我有点得意忘形了。这些是根据CompletableFuture 实现的标准fmap 和join:
<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);
}
【讨论】: