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;
}
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;
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.
package com.geekyarticles.lambda;
public interface SingleArgExpression<P, R> {
public R function(P param);
}
public interface SingleArgExpression<P, R> {
public R function(P param);
}
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;
}
}
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;
}
}
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);
}
}
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);
}
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.
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;
}
}
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;
}
}
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);
}
}
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.
package com.geekyarticles.lambda;
public interface NoArgExpression<R> {
public R function();
}
public interface NoArgExpression<R> {
public R function();
}
package com.geekyarticles.lambda;
public interface SingleArgExpression<P, R> {
public R function(P param);
}
public interface SingleArgExpression<P, R> {
public R function(P param);
}
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();
}
}
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();
}
}
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();
}
}
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.
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;
}
}
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;
}
}
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();
}
}
}
}
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.
1 comments:
Also, this is a thing in Java 8 http://download.java.net/jdk8/docs/api/java/util/Optional.html
Post a Comment