Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to flip an Option<Try<Foo>> to a Try<Option<Foo>>

I have an Try<Option<Foo>>. I want to flatMap Foo into a Bar, using it using an operation that can fail. It's not a failure if my Option<Foo> is an Option.none(), (and the Try was a success) and in this case there's nothing to do.

So I have code like this, which does work:

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

I then call it like:

fooOptionTry.flatMap(this::myFlatMappingFunc);

This does work, but it looks really ugly.

Is there a better way to flip the Try and Option around?


Note 1: I actively do not want to call Option.get() and catch that within the Try as it's not semantically correct. I suppose I could recover the NoSuchElementException but that seems even worse, code-wise.


Note 2 (to explain the title): Naively, the obvious thing to do is:

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

except this has the wrong signature and doesn't let me map with the previous operation that could have failed and also returned a successful lack of value.

like image 923
durron597 Avatar asked Mar 31 '18 01:03

durron597


1 Answers

When you are working with monads, each monad type combine only with monads of same type. This is usually a problem because the code will come very unreadable.

In the Scala world, there are some solutions, like the OptionT or EitherT transformers, but do this kind of abstractions in Java could be difficult.

The simple solution is to use only one monad type.

For this case, I can think in two alternatives:

  1. transform fooOpt to Try<Foo> using .toTry()
  2. transform both to Either using .toEither()

Functional programmers are usually more comfortable with Either because exceptions will have weird behaviors, instead Either usually not, and both works when you just want to know why and where something failed.

Your example using Either will look like this:

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);
}
like image 81
C. Daniel Sanchez Avatar answered Oct 05 '22 11:10

C. Daniel Sanchez