在 Java 8 中尝试 monad
Try monad in Java 8
是否有对处理异常处理的 monad 的内置支持?类似于 Scala 的 Try。我问是因为我不喜欢未经检查的异常。
您可以通过 (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");
}
});
GitHub 上的 "better-java-monads" 项目有一个用于 Java 8 here 的 Try monad。
这里有一个可以用作模型的实现。
可以在此处找到更多信息:
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 风格的 monads,像这样:
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);
}
至少有两个普遍可用(例如在 Maven Central 上)- Vavr and Cyclops 两者都有采用略有不同方法的 Try 实现。
Vavr's Try 非常接近 Scala 的 Try。它将捕获在其组合器执行期间抛出的所有 'non-fatal' 异常。
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);
还有另一个示例 'lifting' 现有方法(可能除以零)以支持错误处理。
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 -> 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 法则。
是否有对处理异常处理的 monad 的内置支持?类似于 Scala 的 Try。我问是因为我不喜欢未经检查的异常。
您可以通过 (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");
}
});
GitHub 上的 "better-java-monads" 项目有一个用于 Java 8 here 的 Try monad。
这里有一个可以用作模型的实现。 可以在此处找到更多信息:
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 风格的 monads,像这样:
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);
}
至少有两个普遍可用(例如在 Maven Central 上)- Vavr and Cyclops 两者都有采用略有不同方法的 Try 实现。
Vavr's Try 非常接近 Scala 的 Try。它将捕获在其组合器执行期间抛出的所有 'non-fatal' 异常。
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);
还有另一个示例 'lifting' 现有方法(可能除以零)以支持错误处理。
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 -> 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 法则。