我有Try<Option<Foo>>
。我希望flatMap
Foo
加入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<?>)
}
然后我称之为:
fooOptionTry.flatMap(this::myFlatMappingFunc);
这确实有效,但看起来很难看。
Try
和Option
?注意1:我主动不想调用Option.get()
并在Try
内捕获它,因为它在语义上不正确。我想我可以恢复NoSuchElementException
,但这似乎更糟糕,代码方面。
注2(解释标题):天真地,显而易见的事情是:
Option<Try<Bar>> myFlatMappingFunc(Option<Foo> fooOpt) {
return fooOpt.map(foo -> mappingFunc(foo));
}
除了这个签名错误,并且不允许我使用之前可能失败的操作进行映射,并且还返回成功缺乏价值。
答案 0 :(得分:1)
当您使用monad时,每个monad类型仅与相同类型的monad组合。这通常是一个问题,因为代码将变得非常难以理解。
在Scala世界中,有一些解决方案,比如OptionT
或EitherT
变换器,但是Java中的这种抽象可能很难。
简单的解决方案是只使用一种monad类型。
对于这种情况,我可以考虑两种选择:
Try<Foo>
函数式程序员通常对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);
}
答案 1 :(得分:0)
我认为这只是您正在寻找的sequence
函数(https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/control/Try.html#sequence-java.lang.Iterable-):
Try.sequence(optionalTry)
答案 2 :(得分:0)
你可以结合Try.sequence和headOption函数,创建一个新的转换函数,看起来更好看,在我看来,你也可以使用泛型类型来获得更可重用的函数:):
private static <T> Try<Option<T>> transform(Option<Try<T>> optT) {
return Try.sequence(optT.toArray()).map(Traversable::headOption);
}
答案 3 :(得分:0)
如果我理解正确,那么您想:
以这种方式分解函数不是更简单:
public void keepOriginalFailureAndSwapSecondOneToEmpty() {
Try<Option<Foo>> tryOptFoo = null;
Try<Option<Bar>> tryOptBar = tryOptFoo
.flatMap(optFoo ->
tryOptionBar(optFoo)
);
}
private Try<Option<Bar>> tryOptionBar(Option<Foo> optFoo) {
return Try.of(() -> optFoo
.map(foo -> toBar(foo)))
.orElse(success(none())
);
}
Bar toBar(Foo foo) throws RuntimeException {
return null;
}
static class Bar {
}
static class Foo {
}
答案 4 :(得分:0)
无所事事和durron597的解决方案帮助了我。这是我的常规测试用例:
def "checkSomeTry"() {
given:
def ex = new RuntimeException("failure")
Option<Try<String>> test1 = Option.none()
Option<Try<String>> test2 = Option.some(Try.success("success"))
Option<Try<String>> test3 = Option.some(Try.failure(ex))
when:
def actual1 = Try.sequence(test1).map({ t -> t.toOption() })
def actual2 = Try.sequence(test2).map({ t -> t.toOption() })
def actual3 = Try.sequence(test3).map({ t -> t.toOption() })
then:
actual1 == Try.success(Option.none())
actual2 == Try.success(Option.some("success"))
actual3 == Try.failure(ex)
}