Как перевернуть опцию<Try <Foo >> в Try<Option <Foo >>

У меня есть Try<Option<Foo>>, я бы хотел flatMapFoo в 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));
}

за исключением того, что он имеет неправильную подпись и не позволяет мне сопоставить предыдущую операцию, которая могла быть неудачной, а также вернула успешное отсутствие значения.

3 ответа

Когда вы работаете с монадами, каждый тип монады комбинируется только с монадами одного типа. Обычно это проблема, потому что код будет очень нечитаемым.

В мире Scala есть несколько решений, таких как OptionT или же EitherT трансформеры, но сделать такого рода абстракции в Java может быть сложно.

Простое решение - использовать только один тип монады.

Для этого случая я могу думать в двух вариантах:

  1. преобразовать fooOpt в Try<Foo> используя .toTry()
  2. преобразовать оба в Either, используя.toEither()

Функциональным программистам обычно удобнее использовать 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);
}

Я считаю, что это просто sequence функция ( https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/control/Try.html), которую вы ищете:

Try.sequence(optionalTry)

Мне там помогло решение 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)
}

Вы можете комбинировать функции Try.sequence и headOption и создавать новую функцию преобразования с немного лучшим внешним видом, на мой взгляд, также вы можете использовать универсальные типы для получения более повторно используемой функции:):

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

Если я правильно понял, вы хотите:

  • оставь первую неудачу, если случится
  • замените второй при сопоставлении с 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 {

    }


Другие вопросы по тегам