javajava-streamvavr

Vavr: Howto flatmap collection inside optional object


Is any easiest way to write this code below, without using toStream()?

import io.vavr.collection.List;
import io.vavr.control.Option;
import lombok.Value;

public class VavrDemo {

    public static void main(String[] args) {
        Foo bar = new Foo(List.of(new Bar(1), new Bar(2)));
        Number value = Option.some(bar)
                .toStream()              // <- WTF?!?
                .flatMap(Foo::getBars)
                .map(Bar::getValue)
                .sum();
        System.out.println(value);
    }

    @Value
    static class Foo {
        private List<Bar> bars;
    }

    @Value
    static class Bar {
        private int value;
    }
}

Solution

  • Option is a so-called Monad. This just tells us that the flatMap function follows specific laws, namely

    Let

    Then all instances of the Monad interface should obey the Functor laws (omitted here) and the three control laws:

    Currently Vavr has (simplified):

    interface Option<T> {
        <U> Option<U> flatMap(Function<T, Option<U>> mapper) {
            return isEmpty() ? none() : mapper.apply(get());
        }
    }
    

    This version obeys the Monad laws.

    It is not possible to define an Option.flatMap the way you want that still obeys the Monad laws. For example imagine a flatMap version that accepts a function with an Iterable as result. All Vavr collections have such a flatMap method but for Option it does not make sense:

    interface Option<T> {
        <U> Option<U> flatMap(Function<T, Iterable<U>> mapper) {
            if (isEmpty()) {
                return none();
            } else {
                Iterable<U> iterable = mapper.apply(get());
                if (isEmpty(iterable)) {
                    return none();
                } else {
                    U resultValue = whatToDoWith(iterable); // ???
                    return some(resultValue);
                }
            }
        }
    }
    

    You see? The best thing we can do is to take just one element of the iterable in case it is not empty. Beside it does not give use the result you may have expected (in VavrTest above), we can proof that this 'phantasy' version of flatMap does break the Monad laws.

    If you are stuck in such a situation, consider to change your calls slightly. For example the VavrTest can be expressed like this:

    Number value = Option.some(bar)
        .map(b -> b.getBars().map(Bar::getValue).sum())
        .getOrElse(0);
    

    I hope this helps and the Monad section above does not completely scare you away. In fact, developers do not need to know anything about Monads in order to take advantage of Vavr.

    Disclaimer: I'm the creator of Vavr (formerly: Javaslang)