Как перевернуть опцию<Try <Foo >> в Try<Option <Foo >>
У меня есть 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));
}
за исключением того, что он имеет неправильную подпись и не позволяет мне сопоставить предыдущую операцию, которая могла быть неудачной, а также вернула успешное отсутствие значения.
3 ответа
Когда вы работаете с монадами, каждый тип монады комбинируется только с монадами одного типа. Обычно это проблема, потому что код будет очень нечитаемым.
В мире Scala есть несколько решений, таких как OptionT
или же EitherT
трансформеры, но сделать такого рода абстракции в Java может быть сложно.
Простое решение - использовать только один тип монады.
Для этого случая я могу думать в двух вариантах:
- преобразовать fooOpt в
Try<Foo>
используя .toTry() - преобразовать оба в 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 {
}