好的,所以我是一名长期的 OO 开发人员,试图进入这个“新发现”的函数式编程世界 - 至少,与此相关,我正在尝试编写类似Java 领域的代码null
并且throw
不存在通过玩弄Option
和Either
单子。(至少,我认为它们是单子;我仍然对这个术语感到不舒服,无论我是否正确使用它......)我要离开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);
}
天真地,使用Either
monad,我期待能够做这样的事情:
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);
(尽管可能使用不同的方法)这将
- 在处理 Either.left 后随时停止处理(即调用适当的
doSomethingWithXException
方法并停止) - 只要 Either.right 存在(如果相关函数返回 Option,则不是 Option.none()),继续整个链。
- 只有
doSomethingWithAwesomeStuffException
在调用失败时才调用awesomeStuff
- 如果任何其他方法失败,它将无法到达。
我正在尝试做的事情是否合理?我的意思是,我确信这可能是某种方式,但是尝试将其硬塞进Java语法是否太复杂了,即使使用这些库中的任何一个(或其他我不知道的库)?