4

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;
    }
}
MariuszS
  • 30,646
  • 12
  • 114
  • 155
  • Why is converting to Stream a problem for you? – Nándor Előd Fekete Sep 14 '17 at 18:58
  • Because generally I can do `Option.some(bar).flatMap(foo -> foo.getBars())`, but in this case I have compilation error. – MariuszS Sep 14 '17 at 19:13
  • Error:(11, 52) java: incompatible types: cannot infer type-variable(s) U (argument mismatch; bad return type in lambda expression io.vavr.collection.List cannot be converted to io.vavr.control.Option extends U>) – MariuszS Sep 14 '17 at 19:14
  • 1
    With `Option` you can map/flatMap, but you'll still stay in `Option`, which is a 0/1 valued monadic container. But you want to switch over to a multi-valued container to do the summation, so you'll need to switch over some time, or you can stay in `Option` but then you would need to do the summation yourself, resulting in an `Option`. – Nándor Előd Fekete Sep 14 '17 at 19:19

2 Answers2

15

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)

Daniel Dietrich
  • 2,262
  • 20
  • 25
  • For Option in Vavr, what is `unit`? Is it Option::of, Option::some, or something else? If I'm following you correctly, calling `.flatMap(Option::of)` on Some(null) would not satisfy the right identity law. – Ben Larson Sep 07 '21 at 18:47
1

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();
Ben Larson
  • 404
  • 1
  • 5
  • 11