Monads

based on

FUNCTIONAL PROGRAMING AND EVENT SOURCING - A PAIR MADE IN HEAVEN by Paweł Szulc

and

MonadicJava by MarioFusco

WTF Monads?

Burrito?

  • We understand code
  • It is easier to talk about related ideas and then abstract to something more general
  • Having abstract model created, we tend to create metaphors
  • Having all above we can formalize

Let's talk code

Finding car insurance's name

Model

public class Person {
    private Car car;

    public Car getCar() {
        return car;
    }
}
public class Car {
    private Insurance insurance;

    public Insurance getInsurance() {
        return insurance;
    }
}

Finding car insurance's name

Model p.2

public class Insurance {
    private String name;

    public String getName() {
        return name;
    }
}

Finding car insurance's name

Implementation

public interface InsuranceFinder {

    String findInsuranceName(Person person);

}
public class NaiveInsuranceFinder implements InsuranceFinder {

    public String findInsuranceName(Person person) {
        return person.getCar().getInsurance().getName();
    }
    
}

Problem?

Person notInsured = new Person(new Car(null));
InsuranceFinder finder = new NaiveInsuranceFinder();
finder.findInsuranceName(notInsured);
Person notInsured = new Person(new Car(null));
InsuranceFinder finder = new NaiveInsuranceFinder();
finder.findInsuranceName(notInsured);
java.lang.NullPointerException
	at org.insurance.finder.impl.NaiveInsuranceFinder.findInsuranceName(NaiveInsuranceFinder.java:9)
	at org.insurance.finder.impl.NaiveInsuranceFinderTest.testFindInsurance(NaiveInsuranceFinderTest.java:21)

Solution?

public class DeepDoubtsInsuranceFinder implements InsuranceFinder {

    public String findInsuranceName(Person person) {
        if (person != null) {
            Car car = person.getCar();
            if (car != null) {
                Insurance insurance = car.getInsurance();
                if (insurance != null) {
                	String insuranceName = insurance.getName();
                    if (insuranceName != null) {
                    	return insuranceName;
                    }
                }
            }
        }

        return "Unknown";
    }
}
public class TooManyChoicesInsuranceFinder implements InsuranceFinder {
    private static final String UNKNOWN = "Unknown";

    public String findInsuranceName(Person person) {
        if (person == null) 
            return UNKNOWN;

        Car car = person.getCar();
        if (car == null)
            return UNKNOWN;

        Insurance insurance = car.getInsurance();
        if (insurance == null) 
            return UNKNOWN;

		String insuranceName = insurance.getName();
        if (insuranceName == null)
        	return UNKNOWN;

        return insuranceName;
    }
}

Call me: Maybe

public class Optional<T> {
    private static final Optional<?> EMPTY = new Optional<>(null);
    private T value;

    private Optional(T value) { this.value = value; }

    public static <T> Optional<T> of(T value) {
        return value == null ? EMPTY : new Optional<>(value);
    }

    public boolean isPresent() {
        return value != null;
    }

    public T get() {
        if (value == null)
            throw new NoSuchElementException("No value");
        return value;
    }

	public T orElse(T elseValue) {
        return isPresent() ? value : elseValue;
    }

	public <U> Optional<U> flatMap(Function<T, Optional<U>> function) {
        Objects.requireNonNull(function);
        if (isPresent())
            return Objects.requireNonNull(function.apply(value));
        return EMPTY;
    }

    public <U> Optional<U> map(Function<T, U> function) {
        Objects.requireNonNull(function);
        if (isPresent())
            return of(function.apply(value));	
        return EMPTY;
    }

}

Rethinking the model

public class Person {
    private Optional<Car> car;

    public Optional<Car> getCar() {
        return car;
    }
}
public class Car {
    private Optional<Insurance> insurance;

    public Optional<Insurance> getInsurance() {
        return insurance;
    }
}

Rethinking the model p.2

public class Insurance {
    private final String name;

    public Insurance(final String name) {
    	Objects.requireNonNull(name);
    	this.name = name;
    }

    public String getName() {
        return name;
    }
}
public interface InsuranceFinder {
    String findInsuranceName(Optional<Person> person);
}

How use it?

Implementation

public class NotExactlyCorrectInsuranceFinder implements InsuranceFinder {
    @Override
    public String findInsuranceName(Optional<Person> person) {
        if (person.isPresent()) {
            Optional<Car> car = person.get().getCar();
            if (car.isPresent()) {
                Optional<Insurance> insurance = car.get().getInsurance();
                if (insurance.isPresent()) {
                    return insurance.get().getName();
                }
            }
        }
        return "Unknown";
    }
}

Implementation

public class MonadicInsuranceFinder implements InsuranceFinder {
    public String findInsuranceName(Optional<Person> person) {
        return person.flatMap(new Function<Person, Optional<Car>>() {
                    public Optional<Car> apply(Person person1) {
                        return person1.getCar();
                    }
                }).flatMap(new Function<Car, Optional<Insurance>>() {
                    public Optional<Insurance> apply(Car car) {
                        return car.getInsurance();
                    }
                }).map(new Function<Insurance, String>() {
                    public String apply(Insurance insurance) {
                        return insurance.getName();
                    }
                }).orElse("Unknown");
    }
}

... or in Java 8

public class MonadicInsuranceFinder implements InsuranceFinder {

    @Override
    public String findInsuranceName(Optional<Person> person) {
        return person
                .flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("Unknown");
    }

}

How does it works?

public class MonadicInsuranceFinder implements InsuranceFinder {
	    public String findInsuranceName(Optional<Person> person) {
	        return person
	                .flatMap(Person::getCar)
	                .flatMap(Car::getInsurance)
	                .map(Insurance::getName)
	                .orElse("Unknown");
	    }
	}

How does it works?

public class MonadicInsuranceFinder implements InsuranceFinder {
	    public String findInsuranceName(Optional<Person> person) {
	        return person
	                .flatMap(Person::getCar)
	                .flatMap(Car::getInsurance)
	                .map(Insurance::getName)
	                .orElse("Unknown");
	    }
	}

How does it works?

public class MonadicInsuranceFinder implements InsuranceFinder {
	    public String findInsuranceName(Optional<Person> person) {
	        return person
	                .flatMap(Person::getCar)
	                .flatMap(Car::getInsurance)
	                .map(Insurance::getName)
	                .orElse("Unknown");
	    }
	}

How does it works?

public class MonadicInsuranceFinder implements InsuranceFinder {
	    public String findInsuranceName(Optional<Person> person) {
	        return person
	                .flatMap(Person::getCar)
	                .flatMap(Car::getInsurance)
	                .map(Insurance::getName)
	                .orElse("Unknown");
	    }
	}

How does it works?

public class MonadicInsuranceFinder implements InsuranceFinder {
	    public String findInsuranceName(Optional<Person> person) {
	        return person
	                .flatMap(Person::getCar)
	                .flatMap(Car::getInsurance)
	                .map(Insurance::getName)
	                .orElse("Unknown");
	    }
	}

What else?

Model

public class Dish {
    private int calories;
    private String name;

    public int getCalories() {
        return calories;
    }

    public String getName() {
        return name;
    }
}

Task

public class Java7DishFinder {
    public List<String> findOrderedLowCaloriesDishesNames(List<Dish> menu) {
        List<Dish> lowCaloriesDishes = new ArrayList<>();
        for (Dish dish : menu) {
            if (dish.getCalories() < 400)
                lowCaloriesDishes.add(dish);
        }
        Collections.sort(lowCaloriesDishes, new Comparator<Dish>() {
            public int compare(Dish d1, Dish d2) {
                return Integer.compare(d1.getCalories(), d2.getCalories());
            }
        });
        List<String> lowCaloriesDishesNames = new ArrayList<>();
        for (Dish dish : lowCaloriesDishes) {
            lowCaloriesDishesNames.add(dish.getName());
        }
        return lowCaloriesDishesNames;
    }
}

Task - Stream Monad

public class DishFinder {

    public List<String> findOrderedLowCaloriesDishes(List<Dish> menu) {
        return menu
                .stream()
                .filter(d -> d.getCalories() < 400)
                .sorted(Comparator.comparing(Dish::getCalories))
                .map(Dish::getName)
                .collect(Collectors.toList());
    }

}
  • We've seen two wrapping classes
  • All have mapping methods
  • Each can take a function that can be called on a value wrapped by the structure
  • Both are MONADS so...

WTF Monads?

  • It is a container, a box
  • A box that can store a value
  • A box that has a common interface that allows us to do one thing: connect sequence of operations on the content of the box
  • But the way it is done depends on a box

Or by definition

Monad is an abstract data type that has two operations: unit and bind

M<A> unit(A, a);
M<B> bind(M<A> ma, Function<A, M<B>> f);
interface M {
	default M<B> map(Function<A, B> f) {
		return bind(x -> unit(f.apply(x)));
	}

	M<B> bind(Function<A, M<B>> f)
}

Profit?

  • Reduce code duplication
  • Improve maintainability
  • Increase readibility
  • Remove side effects
  • Hide complexity
  • Encapsulate implementation details

Using monads...

with Java < 8

  • Use Guava's Optional
  • has transform - something between map and flatMap
  • For manipulating collections use lambdaj

with Java ≥ 8

  • Java's Optional, Stream, ComputableFuture
  • com.jason-goodwin:better-monads - has Try monad (!) and stream od ComputableFuture
  • write your own?

Thank you