如何翻转选项<try <foo>&gt;试试<option <foo>&gt;

时间:2018-03-31 01:27:17

标签: java java-8 monads vavr

我有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);

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

是否有更好的方法来翻转TryOption

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

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

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

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

5 个答案:

答案 0 :(得分:1)

当您使用monad时,每个monad类型仅与相同类型的monad组合。这通常是一个问题,因为代码将变得非常难以理解。

在Scala世界中,有一些解决方案,比如OptionTEitherT变换器,但是Java中的这种抽象可能很难。

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

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

  1. 使用.toTry()
  2. 将fooOpt转换为Try<Foo>
  3. 将两者都转换为使用.toEither()
  4. 函数式程序员通常对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)

如果我理解正确,那么您想:

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

    }


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