Kerflyn's Blog

Well… It's a blog!

From Option to Monad with Java Only

In a previous post, I’ve shown how to use the type Optional provided by Guava, in a view to create a monad. Personally, I’m not satisfied with the resulting code. Since, I’ve found another way to represent the Option monad in Java only, and the new representation seems to be better.

So we start with an interface to represent functions (this is the same as the Guava).

public interface Function<T, R> {
  R apply(T input);
}

Then we implement the Option type. This type contains three monadic operations :

  • wrap that converts an object into an Option,
  • andThen that applies a function to the current instance of Option,
  • fail that returns a None instance.

I add also two getter methods : get and isPresent.

public abstract class Option<T> {

  // opérations de base

  public abstract T get();

  public abstract boolean isPresent();

  // opérations monadiques

  public static <T> Option<T> wrap(T element) { return new Some<T>(element); }

  public static <T> Option<T> fail() { return new None<T>(); }

  public abstract <U> Option<U> andThen(Function<T, Option<U>> function);

}

And now, here is the implementation of the classes Some and None.

public class None<T> extends Option<T> {

  public T get() { throw new IllegalStateException(); }

  public boolean isPresent() { return false; }

  public <U> Option<U> andThen(Function<T, Option<U>> function) {
    return new None<U>();
  }

}

public class Some<T> extends Option<T> {

  private T element;

  private Some(T element) { this.element = element; }

  public T get() { return element; }

  public boolean isPresent() { return true; }

  public <U> Option<U> andThen(Function<T, Option<U>> function) {
    return function.apply(this.element);
  }

}

To come back to the example of the previous post, here are implementations of the different accessors that enables you to explore the (ugly) recursive structure made of Java Maps representing a set of products arranged according to their supplier, city, and country:

public static Function<
  Map<String, Map<String, Map<String, Map<String, Product>>>>,
  Option<Map<String, Map<String, Map<String, Product>>>>
>
accessCountry(String country) {
    return getFromKey(country);
}

public static Function<
  Map<String, Map<String, Map<String, Product>>>,
  Option<Map<String, Map<String, Product>>>
>
accessCity(String city) {
    return getFromKey(city);
}

public static Function<
  Map<String, Map<String, Map<String, Product>>>,
  Option<Map<String, Map<String, Product>>>
>
accessCity(String city) {
    return getFromKey(city);
}

public static Function<
  Map<String, Map<String, Product>>,
  Option<Map<String, Product>>
>
accessSupplier(String supplier) {
    return getFromKey(supplier);
}

public static Function<
  Map<String, Product>,
  Option<Product>
>
accessProduct(String productCode) {
    return getFromKey(productCode);
}

Here is the definition of the method getFromKey:

public static <K, V> Function<Map<K, V>, Option<V>> getFromKey(final K key) {
    return new Function<Map<K, V>, Option<V>>() {
        @Override
        public Option<V> apply(Map<K, V> map) {
            V value = map.get(key);
            if (value == null) {
                return Option.none();
            }
            return Option.wrap(value);
        }
    };
}

Now look how the method getProductFrom is simplified:

public Option<Product> getProductFrom(
  Map<String, Map<String, Map<String, Map<String, Product>>>> productsByCountry,
  String country, String city, String supplier, String code) {

  return wrap(productsByCountry)
    .andThen(accessCountry(country))
    .andThen(accessCity(city))
    .andThen(accessSupplier(supplier))
    .andThen(accessProduct(code));
}

But I cheating a little bit with the way monads are usually used. Normally, a succession of process calls within a monad isn’t a linear chaining like in the example above, but a kind of recursive chaining. I can get such a chaining (without the ugliness of the Java verbosity) if I use the lambda expression of Java 8.

public Option<Product> getProductFrom(
  Map<String, Map<String, Map<String, Map<String, Product>>>> productsByCountry,
  String country, String city, String supplier, String code) {

  return getFrom(productsByCountry, country).andThen(
         cities -> getFrom(cities, city).andThen(
         suppliers -> getFrom(suppliers, supplier).andThen(
         products -> getFrom(products, code))));
}

public <K, V> Option<V> getFrom(Map<K, V> map, K key) {
  V value = map.get(key);
  if (value == null) {
    return Option.none();
  }
  return Option.wrap(value);
}

As a remainder, the construct x -> f(x) currently defines a lambda expression in Java 8.

Written by fsarradin

2012/04/23 at 23:19