6

我有一个Try<Option<Foo>>. 我想flatMap Foo进入一个Bar,使用它使用可能失败的操作。如果 myOption<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);

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

有没有更好的方法来翻转TryOption周围?


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


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

Option<Try<Bar>> myFlatMappingFunc(Option<Foo> fooOpt) {
    return fooOpt.map(foo -> mappingFunc(foo));
}

除了这有错误的签名并且不让我映射上一个可能失败的操作并且还成功返回了缺少值的操作。

4

5 回答 5

1

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

在 Scala 世界中,有一些解决方案,例如OptionTorEitherT转换器,但在 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);
}
于 2018-04-01T03:23:30.127 回答
0

如果我理解正确,您想要:

  • 如果发生,保持第一次失败
  • 映射到 json 时将第二个交换为空选项。

如果您以这种方式分解您的功能,这不是更简单:

    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 {

    }


于 2019-10-25T07:06:25.660 回答
0

我相信这只是你的一个sequence功能(https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/control/Try.html#sequence-java.lang.Iterable-)寻找:

Try.sequence(optionalTry)

于 2018-04-23T18:56:48.250 回答
0

throughnothing 和 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)
}
于 2020-04-01T11:07:06.720 回答
0

您可以结合 Try.sequence 和 headOption 函数并创建一个外观更好的新变换函数,在我看来,您也可以使用泛型类型来获得更可重用的函数:):

private static <T> Try<Option<T>> transform(Option<Try<T>> optT) {
    return Try.sequence(optT.toArray()).map(Traversable::headOption);
}
于 2018-06-09T17:12:52.697 回答