在 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

fmapjoin

的实现

我有点忘乎所以了。这些是根据 CompletableFuture:

实施的标准 fmapjoin
<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 法则。