3

好的,所以我是一名长期的 OO 开发人员,试图进入这个“新发现”的函数式编程世界 - 至少,与此相关,我正在尝试编写类似Java 领域的代码null并且throw不存在通过玩弄OptionEither单子。(至少,我认为它们是单子;我仍然对这个术语感到不舒服,无论我是否正确使用它......)我要离开Atlassian Fugue图书馆了;我尝试查看函数式 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);
}

天真地,使用Eithermonad,我期待能够做这样的事情:

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

...其中每个逻辑方法都将返回一个包含适当异常(可能是特定于域的错误类,但现在异常工作得很好)作为左侧和Option<something>右侧的 Either。这些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);

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

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语法是否太复杂了,即使使用这些库中的任何一个(或其他我不知道的库)?

4

1 回答 1

2

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

话虽如此,在 Java 中执行这种逻辑存在很多问题,即 Java 没有任何模式匹配或任何变体/ ADT /案例类

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

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

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

于 2016-05-04T22:32:32.280 回答