简体   繁体   中英

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 .

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");

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.

You'll want to adapt this to your code, but this is an approach I'd use.

First, isolate the errors you want to encapsulate for each point of failure.

Supplier<? extends RuntimeException> missingAException = IllegalStateException::new;
Supplier<? extends RuntimeException? missingBException = IllegalStateException::new;

Doing this allows you to write a lambda later on to provide a specific error message if you so desire.

Now, we write the optionals.

Optional<A> optA = finder.find(input);
Optional<B> optB = finder.findB(optA.orElseThrow(missingAException));

To extract optB , use the same pattern as we did for optA .

B value = optB.orElseThrow(missingBException);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM