Monday, March 21, 2016

Guava's Optional defficiencies

TL;DR: There is no reason to use Optional from Guava, Java 8 is better.

Guva used to be a most handy toolbox for modern (post-Java 5) projects. It was able to leverage new features and quickly became even more popular than old, good apache-commons (in my opinion some commons classes are still indispensible). Lot of ideas introduced by googlers behind Guava were incorporated into  official Java releases (mostly 8, however in 7 you may find some pieces of API visibly inspired by spirit of Guava helpers).
One of most popular innovations was Guava's approach to bringing Scala's Option[T] into world of Java. I assume idea is known and I hope also readers would agree with me that derogating `null` values would be a benefit for each developer as well for end users. After very enthusiastic reception, some critical voices could have been heard. Namingly Guava's Optional was missing important feature to call it a monad. That was an offence to real functional programmers.

Copernicus agreed with Guava guys - no Flat Map!


Is that problem really that severe? Well, Optional is by now integrated very well with other nice Guava's classes. This is still tempting to use it along with eg. FluentIterable, which has brought Stream-alike API into pre-Java 8 development. It can greatly be used to address deficiency mentioned above. Consider following code:
public static <In, Out> Optional<Out> flatMap(Optional<In> in, Function<In, Out> function) {
  Function<Out, Iterable<Out>> asIterable = asIterable();
  return FluentIterable.from(in.asSet()).transform(function).transformAndConcat(asIterable).first();
}

private static <In> Function<In, Iterable<In>> asIterable() {
  return new Function<In, Iterable<In>>() {
    @Override public Iterable<In> apply(In input) {
       return Optional.fromNullable(input).asSet();
    }
  };
}

Using the function above we managed to implement quite succint version of `flatMap` for Optional. We could now conclude, everything's fine, we have our monad now, and everyone's happy.
But...
There is more problematic issue with this implementation.
The issue that degrades Optional just to simple null pointer warning, built into static code inspection, similarily to what would we gained by declaring checked exception. It however is useless as a flow control tool. Something that we are used to when using Scala's Option, and also much better implemented in java.util.Optional. The very problem is that our `map` function (known here as `transform`) is not really working. Let's verify what would happen when passed an null value to a transformation.
import com.google.common.base.Optional;

public class BrokenGuava {
    public static void main(String[] args) {
        Optional<String> optional = Optional.fromNullable("aaa");
        System.out.println("optional = " + optional);

        Optional<String> transformed = optional.transform(x -> null);
        System.out.println("transformed = " + transformed);
    }
}


which brings us the result of:

optional = Optional.of(aaa)
Exception in thread "main" java.lang.NullPointerException: the Function passed to Optional.transform() must not return null.
 at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:226)
 at com.google.common.base.Present.transform(Present.java:71)
 at options.guava.BrokenGuava.main(BrokenGuava.java:10)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:497)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)


NullPointerException! The same one that we expected to get eradicated by using Optionals all around our code!
The standard has got it implemented in a much better way:

import java.util.Optional;

public class NotSoBrokenUtil {
    public static void main(String[] args) {
        Optional<String> optional = Optional.of("aaa");
        System.out.println("optional = " + optional);

        Optional<String> transformed = optional.map(x -> null);
        System.out.println("mapped = " + transformed);
    }
}

this time you can see:
optional = Optional[aaa]
mapped = Optional.empty

This time it worked much better. Maybe not perfect (as we have empty as result - this is good topic for some next post), but at least not exploded in runtime.
So... Price is the same, why whould I choose suboptimal solution? Time to mark Guava's Optional deprecated.

No comments:

Post a Comment