java - 使用 Fugue/FunctionalJava 摆脱 Null 和 Throws?

标签 java functional-programming monads functional-java

好吧,我是一个长期的 OO 开发人员,试图进入这个“新发现的”函数式编程世界 - 至少,因为它与此有关,我正在尝试像 nullthrow 这样的代码通过玩弄 OptionEither monads,在 Java 领域并不存在。 (至少,我认为它们是单子(monad);我仍然对这个术语感到不自在,无论我是否正确使用它……)我要离开 Atlassian Fugue 库;我试着查看 Functional Java 库,它比我现在准备的要大得多。如果 fj 满足了我的需求而 Fugue 没有,我完全赞成。

基本上,我要完成的工作相当于这个相当丑陋的 Java 代码:

InputObject input = blah();

try {
    final String message = getMessage(input);

    if (message != null) {
        try {
            final ProcessedMessage processedMessage = processMessage(message);
            if (processedMessage != null) {
                try {
                    final ProcessedDetails details = getDetails(notificationDetails);
                    if (details != null) {
                        try {
                            awesomeStuff(details);
                        } catch (AwesomeStuffException ase) {
                            doSomethingWithAwesomeStuffException(ase);
                        }
                    }
                } catch (GetDetailsException gde) {
                    doSomethingWithGetDetailsException(gde);
                }
            }
        } catch (ProcessMessageException pme) {
            doSomethingWithProcessMessageException(pme);
        }
    }
} catch (GetMessageException gme) {
    doSomethingWithGetMessageException(gme);
}

天真地,使用 Either monad,我期望能够做这样的事情:

getMessage(input).fold(this::doSomethingWithGetMessageException, this::processMessage)
              .fold(this::doSomethingWithProcessMessageException, this::getDetails)
              .fold(this::doSomethingWithGetDetailsException, this::awesomeStuff)
              .foldLeft(this::doSomethingWithAwesomeStuffException);

...其中每个逻辑方法都将返回一个 Either,其中包含适当的异常(可能是特定于域的错误类,但异常现在工作得很好)作为左侧,Option<something> 作为右侧. doSomethingWith... 方法将对错误执行他们想要执行的任何日志记录/处理。逻辑方法的一个例子是:

Either<GetMessageException, Option<String>> getMessage(final InputObject input) {
  if (input == null) {
    return Either.left(new GetMessageException("Input was null");
  }

  try {
    return Either.right(loadMessage(input));
  } catch (Exception ex) {
    return Either.left(new GetMessageException(ex));
  }
}

其他方法的定义类似;最好不要Option 作为参数 - 如果先前的方法返回 Option.none,则该方法根本不会被调用。

除了让我无法实际测试它的泛型类型的编译器错误巢穴之外,从逻辑上讲它看起来并不像它实际上做我想做的事 - 我期望事情基本上没有操作在返回第一个 Either.left 值后,但仔细观察它我猜它会继续尝试将 Either.left 结果传递到下一个调用并继续进行下去。

was 仅使用选项就可以实现我的目标:

getMessage(input).map(this::processMessage)
              .map(this::getDetails)
              .map(this::awesomeStuff);

不幸的是,要完成与原始 block 相同的逻辑,逻辑方法将与此类似地实现:

ProcessedMessage processMessage(final String message) {
  try {
    return doProcessing(message);
  } catch (Exception ex) {
    final GetMessageException gme = new GetMessageException(ex);
    doSomethingWithGetMessageException(gme);
    return null;
  }
}

由于 .map 将结果提升为 Option ,因此在此处返回 null 工作正常。不幸的是,我基本上仍然对调用者隐藏了错误状态(更糟糕的是,IMO 使用 null 来表示错误 - 这并没有说明 doProcessing 是否出于潜在的正当理由返回 null)。

因此,从本质上讲,我喜欢具有与前面的 getMessage 示例一致的方法签名 -

Either<GetMessageException, Option<String>> getMessage(final InputObject input)

我喜欢这样的想法,即能够查看返回值并一眼就知道“此方法要么会失败,要么会返回一些可能存在但可能不存在的东西。”但是,我对 FP 太陌生了,不知道如何去做。

我的理解(尽管可能有缺陷)是我可以做类似的事情

getMessage(input).fold(this::doSomethingWithGetMessageException, this::processMessage)
              .fold(this::doSomethingWithProcessMessageException, this::getDetails)
              .fold(this::doSomethingWithGetDetailsException, this::awesomeStuff)
              .foldLeft(this::doSomethingWithAwesomeStuffException);

(尽管可能使用不同的方法)将

  1. 在处理 Either.left 后随时停止处理(即调用适当的 doSomethingWithXException 方法并停止)
  2. 只要 Either.right 存在就继续执行整个链(如果相关函数返回 Option,则不存在 Option.none())。
  3. 在对 doSomethingWithAwesomeStuffException 的调用失败时调用 awesomeStuff - 如果任何其他方法失败,它将无法到达。

我尝试做的事情是否合理?我的意思是,我确信这是可能的,但是尝试将其硬塞进 Java 语法中是否太复杂了,甚至使用这些库中的任何一个(或我不知道的其他库)?

最佳答案

这肯定是一个自以为是的问题。你想要的是 flatMap 也被称为 monad 的绑定(bind)运算符。不幸的是,并不是所有的功能库都这样做(即 Guava 在其 Optional 上没有 flatMap)。 RxJavaReactor确实支持这种类型的操作,但这需要让您的服务更像流(我强烈推荐)。

据说在 Java 中执行这种逻辑存在很多问题,即 Java 没有任何模式匹配或任何 variants/ADTs/case classes .

由于上述原因以及大多数序列化程序在通用容器(即 Jackson)方面存在问题,大多数时候一个好的方法是使用行为 free 不可变的唯一类来返回结果(通常是静态内联类).

我在服务中一直这样做。即创建特定于特定服务请求的内联静态类。您可能会实现接口(interface)以跨内联类重用算法行为,但现实是 Java 对元组/变体/ADT/案例类的支持很糟糕(见上文)。

这通常(但并非总是)比在经常丢失数据的情况下抛出异常要好。

关于java - 使用 Fugue/FunctionalJava 摆脱 Null 和 Throws?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37037867/

相关文章:

haskell - 什么是单子(monad)类别的仿函数?

java - 存储数据表的最佳方式

c++ - 通过模板滥用的函数式 C++

java - jackson - 忽略蛇形 key 中的大小写

F# MailboxProcessor 和功能设计

haskell - 是否有一个直接的解决方案来接收元素 *prior* 击中 dropWhile 谓词?

javascript - 为什么幻想之地规范要求该链必须返回同一链的值?

haskell - 作家 Monad 嵌套两次

java - 如何从 C++ 调用非静态 java 方法?

Java - 捕获html标签