【问题标题】:How to flip an Option<Try<Foo>> to a Try<Option<Foo>>如何将 Option<Try<Foo>> 翻转为 Try<Option<Foo>>
【发布时间】:2018-09-10 00:30:42
【问题描述】:

我有一个Try&lt;Option&lt;Foo&gt;&gt;。我想将flatMapFoo 转换为Bar,使用可能失败的操作。如果我的Option&lt;Foo&gt;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));
}

除了这个有错误的签名并且不允许我与之前可能失败的操作进行映射,并且还成功返回了缺少值。

【问题讨论】:

  • 我害怕问一个比我多 2 万代表的人,但是:如果你有工作代码,难道 Code Review 不是一个更好的网站来问这个问题吗?
  • @AJNeufeld 如果代码审查包含真实代码,这可能是一个很好的问题。声明“这样的代码......”的问题看起来像示例代码,这将是题外话。 OP 应该清楚地表明代码是从生产就绪的角度来看的。此外,在 CR 上,您要求审查,不一定是替代方案。评论可以显示替代方案(如果存在)。另见codereview.meta.stackexchange.com/questions/5777/…
  • @durron597 我知道我在伸脖子,在评论之前应该三思而后行。 (JSYK:你有 25k,而不是 20k。我说还有 20k……)
  • @AJNeufeld 永远不会在懒惰的时候假设自己无能

标签: java java-8 monads vavr


【解决方案1】:

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

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

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

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

  1. 使用.toTry() 将fooOpt 转换为Try&lt;Foo&gt;
  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);
}

【讨论】:

  • 我不太喜欢在这里使用Either&lt;String, Foo&gt;,因为String 不够表达。但是你让我考虑完全放弃Option 的概念,只依赖Try,然后,如果我想知道这是否是一个成功的缺乏结果,只需要.recover(NoSuchElementException.class, ...)
  • 同意,我在这里将左侧设置为字符串只是为了方便,但对于 realworldcode™,我们需要更多细节。我通常避免 Try 因为大部分时间我使用 Future monad,它也有一个恢复功能。最后,这将变成Future&lt;Either&lt;ErrorBar, Bar&gt;&gt;。我希望你觉得这很有用(这实际上是我在 SO 中的第一个回复,哈哈)。
  • 另外,您不需要删除 Option monad。每个 monad 都有一个有用的上下文:在异常 Try 的上下文中,在 nulls Option 的上下文中,在线程 Future 的上下文中等等。你只需要在需要组合它们时担心,然后你可以定义一个monad 类型为“通用语言”。可以随心所欲地尝试。
  • 我不是说总是丢,只是丢在这里。
【解决方案2】:

我相信这只是您正在寻找的 sequence 函数 (https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/control/Try.html#sequence-java.lang.Iterable-):

Try.sequence(optionalTry)

【讨论】:

  • 我不是 100% 是否可行,但如果可行,这不会返回 Try&lt;Seq&lt;T&gt;&gt; 而不是 Try&lt;Option&lt;T&gt;&gt;?也许它可以与.toOption() 一起使用?
【解决方案3】:

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

private static <T> Try<Option<T>> transform(Option<Try<T>> optT) {
    return Try.sequence(optT.toArray()).map(Traversable::headOption);
}

【讨论】:

  • 这和我的 Note 1 有什么不同?
  • 在此解决方案中,您不会捕获 NoSuchElementException,如果选项为 none,您将成功地尝试使用内部选项 none。
【解决方案4】:

如果我理解正确,你想:

  • 如果发生第一次失败,请保留
  • 在映射到 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 {

    }


【讨论】:

    【解决方案5】:

    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)
    }
    

    【讨论】:

      猜你喜欢
      • 2016-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-15
      • 2019-12-27
      • 1970-01-01
      • 1970-01-01
      • 2015-06-10
      相关资源
      最近更新 更多