6

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.

durron597
  • 31,968
  • 17
  • 99
  • 158
  • 6
    I fear to ask someone with 20 thousand more rep than myself this, but: If you have working code, wouldn’t Code Review be a better site to ask this on? – AJNeufeld Mar 31 '18 at 05:44
  • @AJNeufeld This _could_ be a good question for Code Review if it includes real code. Questions that state "code like this ..." look like example code, which would be off-topic. OP should make clear that the code is from is point of view production-ready. Also, on CR you ask for a review, not necessarily an alternative. A review _can_ show an alternative if one exists. See also https://codereview.meta.stackexchange.com/questions/5777/a-guide-to-code-review-for-stack-overflow-users – Zeta Mar 31 '18 at 06:35
  • @AJNeufeld not only do I have 20k rep, I also am the first author of the community wiki about when to post on Code Review vs Stack Overflow that Zeta linked. – durron597 Apr 01 '18 at 00:19
  • @durron597 I knew I was sticking my neck out, and should think thrice before commenting. (JSYK: you have 25k, not 20k. I said 20k more...) – AJNeufeld Apr 01 '18 at 01:04
  • @AJNeufeld never assume incompetence when laziness will do – durron597 Apr 01 '18 at 01:06
  • Probably check Scala's way to do it. It may give you some ideas for Java. – Jus12 Jun 09 '18 at 17:16
  • @Jus12 You have to use the [Cats](https://github.com/typelevel/cats) library to do it in Scala – durron597 Jun 09 '18 at 17:18
  • @durron597 isn't this similar? https://stackoverflow.com/a/36701612/243233 – Jus12 Jun 09 '18 at 17:20
  • @Jus12 I already addressed why I don't want to do that in Note 1. – durron597 Jun 09 '18 at 17:21

5 Answers5

1

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);
}
  • I don't really like using `Either` here, as `String` is not expressive enough. But you're making me consider dropping the `Option` concept entirely and just rely on `Try` and then later, if I want to know if it was a successful lack of result, to just `.recover(NoSuchElementException.class, ...)` – durron597 Apr 01 '18 at 03:29
  • Agree, I did the left side as String here just for ease, but for realworldcode™, we want more detail. I usually avoid Try because most of time I use the Future monad, and it have a recover function too. Then at end, this will become like `Future>`. I hope you found this useful (it is actually my first response in SO, lol). – C. Daniel Sanchez Apr 01 '18 at 03:45
  • Also, you do not need to drop the Option monad. Each monad have a context where it is useful: in the context of exceptions Try, in the context of nulls Option, in the context of threads Future, etc. You only need to worry when you need to combine them, then you can define a monad type as "lingua franca". It could be Try as you want. – C. Daniel Sanchez Apr 01 '18 at 03:57
  • I didn’t mean drop it always, just drop it here. – durron597 Apr 01 '18 at 04:10
0

I believe this is simply a sequence function (https://static.javadoc.io/io.vavr/vavr/0.9.2/io/vavr/control/Try.html#sequence-java.lang.Iterable-) that you are looking for:

Try.sequence(optionalTry)

  • I'm not 100% if would work, but if it does, wouldn't this return a `Try>` not a `Try – durron597 Apr 23 '18 at 19:53
0

You can combine Try.sequence and headOption functions and create a new transform function with a little better look, in my opinion, also you can use generic types to get a more reusable function :) :

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

If I understand correctly, you want to :

  • keep the first failure if happens
  • swap the second when mapping to json for an empty option.

Isn t it simpler if you decompose your function in such a way:

    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 {

    }


paswar
  • 11
  • 1
0

The solution of throughnothing and durron597 helped me there. This is my groovy test case:

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)
}
Lexor
  • 11
  • 1