Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
    }
}
like image 740
MariuszS Avatar asked Dec 03 '22 21:12

MariuszS


2 Answers

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

Let

  • A, B, C be types
  • unit: A -> Monad<A> a constructor
  • f: A -> Monad<B>, g: B -> Monad<C> functions
  • a be an object of type A
  • m be an object of type Monad<A>

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

  • Left identity: unit(a).flatMap(f) ≡ f a
  • Right identity: m.flatMap(unit) ≡ m
  • Associativity: m.flatMap(f).flatMap(g) ≡ m.flatMap(x -> f.apply(x).flatMap(g))

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)

like image 105
Daniel Dietrich Avatar answered Dec 11 '22 16:12

Daniel Dietrich


How about using .fold() or .getOrElse()?

Option.some(bar)
    .fold(List::<Bar>empty, Foo::getBars)
    .map(Bar::getValue)
    .sum();
Option.some(bar)
    .map(Foo::getBars)
    .getOrElse(List::empty)
    .map(Bar::getValue)
    .sum();
like image 43
Ben Larson Avatar answered Dec 11 '22 15:12

Ben Larson