如何将Option <Try <Foo >>翻转到Try <Option <Foo >>

dur*_*597 6 java monads java-8 vavr

我有一个Try<Option<Foo>>.我想flatMap Foo进入a Bar,使用可能失败的操作使用它.如果我Option<Foo>是一个Option.none()(并且Try是成功的)并且在这种情况下没有任何关系,这不是失败.

所以我有这样的代码,它确实有效:

Try<Option<Bar>> myFlatMappingFunc(Option<Foo> fooOpt) {
    return fooOpt.map(foo -> mappingFunc(foo).map(Option::of) /* ew */)
                 .getOrElse(Try.success(Option.none()); // double ew
}

Try<Bar> mappingFunc(Foo foo) throws IOException {
    // do some mapping schtuff
    // Note that I can never return null, and a failure here is a legitimate problem.
    // FWIW it's Jackson's readValue(String, Class<?>)
}
Run Code Online (Sandbox Code Playgroud)

然后我称之为:

fooOptionTry.flatMap(this::myFlatMappingFunc);
Run Code Online (Sandbox Code Playgroud)

这确实有效,但看起来真的很难看.

有没有更好的方式来翻转TryOption左右?


注1:我主动不想在内部调用Option.get()和捕获它,Try因为它在语义上不正确.我想我可以恢复,NoSuchElementException但这似乎更糟糕,代码方面.


注2(解释标题):天真地,显而易见的事情是:

Option<Try<Bar>> myFlatMappingFunc(Option<Foo> fooOpt) {
    return fooOpt.map(foo -> mappingFunc(foo));
}
Run Code Online (Sandbox Code Playgroud)

除了这有错误的签名,并且不允许我映射可能失败的先前操作并且还返回成功的缺乏值.

小智 1

当您使用 monad 时,每种 monad 类型仅与相同类型的 monad 组合。这通常是一个问题,因为代码非常不可读。

在 Scala 世界中,有一些解决方案,例如OptionTEitherT转换器,但在 Java 中实现这种抽象可能很困难。

简单的解决方案是仅使用一种 monad 类型。

对于这种情况,我可以考虑两种选择:

  1. 将 fooOpt 转换为Try<Foo>使用.toTry()
  2. 使用 .toEither() 将两者转换为 Either

函数式程序员通常更喜欢“Either”,因为异常会产生奇怪的行为,而“Either”通常不会,当您只想知道失败的原因和位置时,两者都适用。

使用 Either 的示例将如下所示:

Either<String, Bar> myFlatMappingFunc(Option<Foo> fooOpt) {
  Either<String, Foo> fooE = fooOpt.toEither("Foo not found.");
  return fooE.flatMap(foo -> mappingFunc(foo));
}

// Look mom!, not "throws IOException" or any unexpected thing!
Either<String, Bar> mappingFunc(Foo foo) {
  return Try.of(() -> /*do something dangerous with Foo and return Bar*/)
    .toEither().mapLeft(Throwable::getLocalizedMessage);
}
Run Code Online (Sandbox Code Playgroud)