Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement this nested flow with optionals?

I've have a method that takes String as an input and should also return a String.

The following ASCII art presents the logical flow:

Option<A> optA = finder.findA(input);

          optA
           /\
isEmpty() /  \ isDefined()  
         /    \
 "ERR_1"       Option<B> optB = finder.findB(optA.get().bid);
                      / \
           isEmpty() /   \ isDefined()
                    /     \
                "ERR_2"    opt2.get().id

Basically for given input I'm looking for A object which is returned wrapped in an Option. Then is A is present I'm looking for B - wrapped in an Option too, otherwise return ERR_1. Then if B is present return it's id, otherwise return ERR_2.

I'm wondering how it could be implemented using optionals (or pattern matching maybe?) in a nice and concise way (without any ifology) - possibly in one-liner.

Could anyone please suggest something?

Source code to try out can be found here.

like image 397
Opal Avatar asked Nov 17 '16 16:11

Opal


2 Answers

It looks like you have 3 possible exit points:

  1. optA empty -> "ERR_1"
  2. optA not empty && optB empty -> "ERR_2"
  3. both not empty -> optB.get().bid

You can achieve this by doing this with Javaslang:

 optA
   .map(a -> finder.findB(a.bid)
      .map(b -> b.bid)
      .getOrElse("ERR_2"))
   .getOrElse("ERR_1");

If optA is empty, we will jump straight to orElse("ERR_1")

If optA is not empty, we are using the value stored inside for getting value b.bid or "ERR_2" in case of optB emptiness.

Also, in pure Java 8, it would look like this:

optA
  .map(a -> finder.findB(a.bid)
    .map(b -> b.bid)
    .orElse("ERR_2"))
  .orElse("ERR_1");
like image 140
Grzegorz Piwowarek Avatar answered Nov 08 '22 01:11

Grzegorz Piwowarek


Since you're using javaslang, Try seems to be a better choice because it propagates the error throught the chain, while Option only propagates its "emptiness".

If you can change findA and findB to return Try you get:

Try<B> b = finder.findA(input)
    .flatMap(a -> finder.findB(a.bid))

If you can't, then:

Try<B> b = finder.findA(input).toTry(() -> new Exception("ERR_1"))
    .flatMap(a -> findB(a.bId).toTry(() -> new Exception("ERR_2")))

That gets a tentative B, I'm unsure if you want to collapse the valid value and the error into the same value, if that's the case then:

String value = b.getOrElseGet(Throwable::getMessage)

If you have an issue with creating pointless exceptions, you can use an Either in each of the find operations, where the left value is your error type. That seems to model the problem better, but may have the downside of longer type signatures, depending on how you split up the expressions.

like image 28
Fred H. Avatar answered Nov 08 '22 02:11

Fred H.