Functional Programming with Java 8 Lambda Expressions – Monads
What is a monad?: A monad is a design pattern concept used in mostly functional programming languages like lisp or in the modern world clojure or scala. (I would in fact copy a few things from scala.) Now why is it becoming important in java? Because java has got its new lambda feature from version 8. Lambda or closure is a functional programming feature. It allowes you to use code blocks as variables and lets you pass it around as such. I have discussed about Java’s ‘Project Lambda’ in my previous article What’s Cooking in Java 8 – Project Lambda. You can now try it out on JDK 8 preview release available in here. Now could we do monads before Java 8? Sure, after all Java’s lambda is semantically just another way of implementing an interface (Its not actually that because the compiler knows where its being used), but it would be a lot messier code which would pretty much kill its utility.
Now, rather than describing an abstract and seemingly meaningless idea to you, let me set up a use-case in Java as it would be without monads.
Pesky null checks: If you have written any non-trivial (like Hello-World) java program, you have probably done some null checks. They are like the necessary evil of programming, you cannot do without them, but they make your program cluttered with noise. Lets take the following example with a set of java data objects. Notice I have not used getters or setter which are anti-patterns anyway.
public static class Userdetails{ public Address address; public Name name; } public static class Name{ public String firstName; public String lastName; } public static class Address{ public String houseNumber; public Street street; public City city; } public static class Street{ public String name; } public static class City{ public String name; }
Now say you want to access the street name from a UserDetails user with the possibility of any property being null. Without monads, you would probably write a code like the following.
if(user == null ) return null; else if(user.address == null) return null; else if(user.address.street == null) return null; else return user.address.street.name;
It ideally should be a one-liner. We have so much noise around the code we really care about. So lets see how we can fix that. Let create a class Option that represents an optional value. And lets then have a map method that will run a lambda on its wrapped value and return another option. If the wrapped value is null, it will return an Option containing null without processing the lambda, thus avaoiding a null pointer exception. Note that the map method needs to actually take a lambda as a parameter, but we will need to create an interface SingleArgExpression to support that.
SingleArgExpression.java
package com.geekyarticles.lambda; public interface SingleArgExpression<P, R> { public R function(P param); }
Option.java
package com.geekyarticles.javamonads; import com.geekyarticles.lambda. public class Option<T> { T value; public Option(T value){ this.value = value; } public <E> Option<E> map(SingleArgExpression<T,E> mapper){ if(value == null){ return new Option<E>(null); }else{ return new Option<E>(mapper.function(value)); } } @Override public boolean equals(Object rhs){ if(rhs instanceof Option){ Option o = (Option)rhs; if(value == null) return (o.value==null); else{ return value.equals(o.value); } }else{ return false; } } @Override public int hashCode(){ return value==null? 0 : value.hashCode(); } public T get(){ return value; } }
OptionExample.java
package com.geekyarticles.javamonads.examples; import com.geekyarticles.javamonads. public class OptionExample{ public static class Userdetails{ public Option<Address> address = new Option<>(null); public Option<Name> name = new Option<>(null); } public static class Name{ public Option<String> firstName = new Option<String>(null); public Option<String> lastName = new Option<String>(null); } public static class Address{ public Option<String> houseNumber; public Option<Street> street; public Option<City> city; } public static class Street{ public Option<String> name; } public static class City{ public Option<String> name; } public static void main(String [] args){ Option<Userdetails> userOpt = new Option<>(new Userdetails()); //And look how simple it is now String streetName = userOpt.flatMap(user -> user.address).map(address -> address.street).map(street -> street.name).get(); System.out.println(streetName); } }
So now, basically the idea is to return an Option whenever a method has the chance of returning null. It will make sure that the consumer of the method understands that the value can be null and also lets the consumer move past null checks implicitly as shown. Now that we are returning Option from all our methods that might have to return null, its likely that the expressions inside the map would also have Option as return type. To avoid calling get() every time, we can have a similar method flatMap that is same as map, except it accepts an Option as a return type for the lambda that is passed to it.
public <E> Option<E> flatMap(SingleArgExpression<T, Option<E>> mapper){ if(value == null){ return new Option<E>(null); } return mapper.function(value); }
The last method I would talk about is filter. It will let us put an if condition in the map chain, so that a value is obtained only when a condition is true. Note that this is also null-safe. The use of filter is not obvious in this particular monad, but we will see its usage later. The following is a sample where all nullable fields have been upgraded to Option and hence flatMap is used instread of map.
Option.java
package com.geekyarticles.javamonads; import com.geekyarticles.lambda. public class Option<T> { T value; public Option(T value){ this.value = value; } public <E> Option<E> map(SingleArgExpression<T,E> mapper){ if(value == null){ return new Option<E>(null); }else{ return new Option<E>(mapper.function(value)); } } public <E> Option<E> flatMap(SingleArgExpression<T, Option<E>> mapper){ if(value == null){ return new Option<E>(null); } return mapper.function(value); } public Option<T> filter(SingleArgExpression<T, Boolean> filter){ if(value == null){ return new Option<T>(null); }else if(filter.function(value)){ return this; }else{ return new Option<T>(null); } } @Override public boolean equals(Object rhs){ if(rhs instanceof Option){ Option o = (Option)rhs; if(value == null) return (o.value==null); else{ return value.equals(o.value); } }else{ return false; } } @Override public int hashCode(){ return value==null? 0 : value.hashCode(); } public T get(){ return value; } }
OptionExample.java
package com.geekyarticles.javamonads.examples; import com.geekyarticles.javamonads. public class OptionExample{ public static class Userdetails{ public Option<Address> address = new Option<>(null); public Option<Name> name = new Option<>(null); } public static class Name{ public Option<String> firstName = new Option<String>(null); public Option<String> lastName = new Option<String>(null); } public static class Address{ public Option<String> houseNumber; public Option<Street> street; public Option<City> city; } public static class Street{ public Option<String> name; } public static class City{ public Option<String> name; } public static void main(String [] args){ //This part is just the setup code for the example to work Option<Userdetails> userOpt = new Option<>(new Userdetails()); userOpt.get().address = new Option<>(new Address()); userOpt.get().address.get().street=new Option<>(new Street()); userOpt.get().address.get().street.get().name = new Option<>("H. Street"); //And look how simple it is now String streetName = userOpt.flatMap(user -> user.address).flatMap(address -> address.street).flatMap(street -> street.name).get(); System.out.println(streetName); } }
Collections and Monads: Monads can be useful for Collection frameworks as well. Although the best way would be for every collection class to be monads themselves for best performance (Which they might become in future), currently we can wrap them up. It also creates a problem of having to break the type cheking system, because we do not know the generic return type of the builder beforehand.
NoArgExpression.java
package com.geekyarticles.lambda; public interface NoArgExpression<R> { public R function(); }
SingleArgExpression.java
package com.geekyarticles.lambda; public interface SingleArgExpression<P, R> { public R function(P param); }
CollectionMonad.java
package com.geekyarticles.javamonads; import com.geekyarticles.lambda. import java.util.Collection; import java.util.ArrayList; import java.util.Arrays; public class CollectionMonad<T> { Collection<T> value; NoArgExpression<Collection> builder; public CollectionMonad(Collection<T> value, NoArgExpression<Collection> builder){ this.value = value; this.builder = builder; } public CollectionMonad(T[] elements){ this.value = new ArrayList<T>(elements.length); this.value.addAll(Arrays.asList(elements)); this.builder = () -> new ArrayList(); } @SuppressWarnings("unchecked") public <E> CollectionMonad<E> map(SingleArgExpression<T,E> mapper){ Collection<E> result = (Collection<E>)builder.function(); for(T item:value){ result.add(mapper.function(item)); } return new CollectionMonad<E>(result, builder); } //What flatMap does is to flatten out the CollectionMonad returned by the lambda that is provided //It really shrinks a nested loop. @SuppressWarnings("unchecked") public <E> CollectionMonad<E> flatMap(SingleArgExpression<T, CollectionMonad<E>> mapper){ Collection<E> result = (Collection<E>)builder.function(); for(T item:value){ CollectionMonad<E> forItem = mapper.function(item); for(E e : forItem.get()){ result.add(e); } } return new CollectionMonad<E>(result, builder); } @SuppressWarnings("unchecked") public CollectionMonad<T> filter(SingleArgExpression<T, Boolean> filter){ Collection<T> result = (Collection<T>)builder.function(); for(T item:value){ if(filter.function(item)){ result.add(item); } } return new CollectionMonad<T>(result, builder); } public Collection<T> get(){ return value; } @Override public String toString(){ return value.toString(); } }
ListMonadTest.java
package com.geekyarticles.javamonads.examples; import com.geekyarticles.javamonads. import java.util. public class ListMonadTest { public static void main(String [] args){ mapExample(); flatMapExample(); filterExample(); } public static void mapExample(){ List<Integer> list = new ArrayList<>(); list.add(10); list.add(1); list.add(210); list.add(130); list.add(2); CollectionMonad<Integer> c = new CollectionMonad<>(list, () -> new ArrayList()); //Use of map System.out.println(c.map(v -> v.toString()).map(v -> v.charAt(0))); System.out.println(); } public static void flatMapExample(){ List<Integer> list = new ArrayList<>(); list.add(10); list.add(1); list.add(210); list.add(130); list.add(2); CollectionMonad<Integer> c = new CollectionMonad<>(list, () -> new ArrayList()); //Use of flatMap System.out.println(c.flatMap(v -> new CollectionMonad<Integer>(Collections.nCopies(v,v), () -> new ArrayList()))); System.out.println(); } public static void filterExample(){ List<Integer> list = new ArrayList<>(); list.add(10); list.add(1); list.add(210); list.add(130); list.add(2); CollectionMonad<Integer> c = new CollectionMonad<>(list, () -> new ArrayList()); //Use of flatMap and filter System.out.println(c.flatMap(v -> new CollectionMonad<Integer>(Collections.nCopies(v,v), () -> new ArrayList())).filter(v -> v<=100)); System.out.println(); } }
At the first glance, it might appear that using flatmap is quite a bit of trouble here because we need to create a CollectionMonad from the lambda. But if you think about the equivalent code with a nested for loop, it still is pretty neat.
Streams and Monads: Well at this point you might be thinking of InputStream(s), but we would discuss something more general than this. A stream is basically a sequence which is possibly infinite. It can be created say for example using a formula, or indeed an InputStream. We will have Streams with hasNext() and next() methods just like Iterator. In fact we will use Iterator interface so we can use the enhanced for loop. But we will also make the stream monads. This case is particularly interesting because streams are possibly infinite, hence the map must return a stream that lazily processes the lambda. In our example, we would create a specialized random number generator with specific distribution. Normally all values are equally probable. But we can change that by mapping. Let see the example to better understand.
Let’s create a generic Stream that can wrap an arbitrary Iterator. That way we can use it existing collection framework as well.
Stream.java
package com.geekyarticles.javamonads; import java.util.Iterator; import com.geekyarticles.lambda. import java.util.NoSuchElementException; public class Stream<T> implements Iterable<Option<T>>, Iterator<Option<T>>{ //Provides a map on the underlying stream private class MapperStream<T,R> extends Stream<R>{ private Stream<T> input; private SingleArgExpression<T, R> mapper; public MapperStream(Stream<T> input, SingleArgExpression<T, R> mapper){ this.input = input; this.mapper = mapper; } @Override public Option<R> next(){ if(!hasNext()){ //This is to conform to Iterator documentation throw new NoSuchElementException(); } return input.next().map(mapper); } @Override public boolean hasNext(){ return input.hasNext(); } } //Provides a flatMap on the underlying stream private class FlatMapperStream<T,R> extends Stream<R>{ private Stream<T> input; private SingleArgExpression<T, Stream<R>> mapper; private Option<Stream<R>> currentStream = new Option<>(null); public FlatMapperStream(Stream<T> input, SingleArgExpression<T, Stream<R>> mapper){ this.input = input; this.mapper = mapper; } @Override public Option<R> next(){ if(hasNext()){ return currentStream.flatMap(stream -> stream.next()); }else{ //This is to conform to Iterator documentation throw new NoSuchElementException(); } } @Override public boolean hasNext(){ if(currentStream.map(s -> s.hasNext()) //Now Option(false) and Option(null) should be treated same .equals(new Option<Boolean>(Boolean.TRUE))){ return true; }else if(input.hasNext()){ currentStream=input.next().map(mapper); return hasNext(); }else{ return false; } } } //Puts a filter on the underlying stream private class FilterStream<T> extends Stream<T>{ private Stream<T> input; private SingleArgExpression<T, Boolean> filter; private Option<T> next = new Option<>(null); public FilterStream(Stream<T> input, SingleArgExpression<T, Boolean> filter){ this.input = input; this.filter = filter; updateNext(); } public boolean hasNext(){ return next != null; } //We always keep one element calculated in advance. private void updateNext(){ next = input.hasNext()? input.next(): new Option<T>(null); if(!next.map(filter).equals(new Option<Boolean>(Boolean.TRUE))){ if(input.hasNext()){ updateNext(); }else{ next = null; } } } public Option<T> next(){ Option<T> res = next; updateNext(); if(res == null){ throw new NoSuchElementException(); } return res; } } protected Iterator<T> input; public Stream(Iterator<T> input){ this.input=input; } //Dummy constructor for the use of subclasses protected Stream(){ } @Override public boolean hasNext(){ return input.hasNext(); } @Override public Option<T> next(){ return new Option<>(input.next()); } @Override public void remove(){ throw new UnsupportedOperationException(); } public <R> Stream<R> map(SingleArgExpression<T,R> mapper){ return new MapperStream<T, R>(this, mapper); } public <R> Stream<R> flatMap(SingleArgExpression<T, Stream<R>> mapper){ return new FlatMapperStream<T, R>(this, mapper); } public Stream<T> filter(SingleArgExpression<T, Boolean> filter){ return new FilterStream<T>(this, filter); } public Iterator<Option<T>> iterator(){ return this; } }
StreamExample.java
package com.geekyarticles.javamonads.examples; import com.geekyarticles.javamonads. import java.util. public class StreamExample{ public static void main(String [] args){ iteratorExample(); infiniteExample(); } static void iteratorExample(){ System.out.println("iteratorExample"); List<Integer> l = new ArrayList<>(); l.addAll(Arrays.asList(new Integer[]{1,2,5,20,4,51,7,30,4,5,2,2,1,30,9,2,1,3})); Stream<Integer> stream = new Stream<>(l.iterator()); //Stacking up operations //Multiply each element by 10 and only select if less than 70 //Then take the remainder after dividing by 13 for(Option<Integer> i : stream.map(i -> i*10).filter(i -> i < 70).map(i -> i%13)){ System.out.println(i.get()); } System.out.println(); } static void infiniteExample(){ System.out.println("infiniteExample"); Iterator<Double> randomGenerator = new Iterator<Double>(){ @Override public Double next(){ return Math.random(); } @Override public boolean hasNext(){ //Infinite iterator return true; } public void remove(){ throw new UnsupportedOperationException(); } }; Stream<Double> randomStream = new Stream<>(randomGenerator); //Now generate a 2 digit integer every second, for ever. for(Option<Integer> val:randomStream.map(v -> (int)(v*100))){ System.out.println(val.get()); try{ Thread.sleep(1000); }catch(InterruptedException ex){ ex.printStackTrace(); } } } }
This example is fairly complex, so spend some time reading this. However, the Stream class needs to be created only once. Once its there, it can used to wrap any Iterator and it will give you all the monadic features for free.
In my next post, I would explain some more monads.
A critique here. I don’t think there’s enough explanation of what exactly a monad is here. Your intro doesn’t go any deeper than to say it’s a functional programming design pattern. In your code examples or surrounding text, I don’t see anything which states “This is a monad.” I only see some classes that happen to be named [Something]Monad. So despite your good intentions, I don’t see anything to show how nomads (which are not clearly defined) change Java programming. I believe this could help with better code comments around the examples you’re trying to illustrate, highlighting them from the… Read more »
Dear Debasish Ray Chawdhuri!
There are neither monads nor monad tutorial. This is just flatmapping which is just a method of the monad typeclass.
Did you read the tutorial?