【问题标题】:Try monad in Java 8在 Java 8 中尝试 monad
【发布时间】:2015-01-05 20:57:22
【问题描述】:

是否有对处理异常处理的 monad 的内置支持?类似于 Scala 的Try。我问是因为我不喜欢未经检查的异常。

【问题讨论】:

    标签: java-8 monads


    【解决方案1】:

    至少有两个普遍可用的(例如在 Maven Central 上) - VavrCyclops 都有 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;
     }
    

    【讨论】:

      【解决方案2】:

      GitHub 上的“better-java-monads”项目有一个 Try monad for Java 8 here

      【讨论】:

      • 有时候做点什么还是有用的。这可以使用 Java 语法中的 finally 子句来指定。这些单子库是否有类似的东西可以达到相同的效果?
      【解决方案3】:

      首先,让我为回答而不是评论而道歉 - 显然我需要 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 法则。

      【讨论】:

        【解决方案4】:

        您可以通过 (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");
            }
        });
        

        【讨论】:

        • “请不要在任何形式的生产代码中这样做。”如果您解释原因会有所帮助...您的意思是不要为除法操作这样做还是不要使用 CompletableFuture有吗?
        • @misha 我也想知道。有时你需要改变规则来维持生计,但你需要知道其中的危险。
        • @alexandroid CompletableFuture 用于异步执行,像这样使用它只会令人困惑。
        【解决方案5】:

        这里有一个可以用作模型的实现。 更多信息可以在这里找到:

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

        我希望这能遮住一些光。

        【讨论】:

          【解决方案6】:

          @Misha 正在做某事。显然你不会在真正的代码中做这件事,但是CompletableFuture 提供了这样的 Haskell 风格的 monad:

          • return 映射到 CompletableFuture.completedFuture
          • &gt;= 映射到 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);
          }
          

          【讨论】:

            猜你喜欢
            • 2019-05-30
            • 2016-07-02
            • 1970-01-01
            • 2015-11-10
            • 2014-11-05
            • 1970-01-01
            • 2023-04-11
            • 2023-03-18
            • 1970-01-01
            相关资源
            最近更新 更多