Skip to content

Generic Monad Operations

johnmcclean-aol edited this page Feb 24, 2016 · 6 revisions

Cyclops has merged with simple-react. Please update your bookmarks (stars :) ) to https://github.com/aol/cyclops-react

All new develpoment on cyclops occurs in cyclops-react. Older modules are still available in maven central.

screen shot 2016-02-22 at 8 44 42 pm

The Monad Interface

Cyclops defines a class AnyM that can wrap any Monad implementation (the JDK comes with three : Stream, Optional and CompletableFuture). It provides a common way to execute monad operators such map / flatMap / peek / filter etc.

AnyM instances can be created by the companion class AnyMonads.

 AnyM<String> handlesStrings = AnyMonads.anyM(Stream.of("hello","world"));

 AnyM<String> handlesStrings = AnyMonads.anyM(Optional.of("hello"));   

 AnyM<String> handlesStrings = AnyMonads.anyM(CompletableFuture.supplyAsync(()->"hello"));  

For external Monad types, you should define your own typed creational method (that can indirectly call AnyMonads.notTypeSafeAnyM( )). You should also define a Comprehender for your Monad (although it is possible if the method naming and behaviour follow the map / flatMap/ of pattern found in the JDK that the built in InvokeDynamicComprehender will work with it out of the box).

We can now write code that accepts any Monad type and process it in a generic way. E.g. To take a very simple example, the method below can accept any Monad type that processes Strings.

  public AnyM<String> toUpperCase(AnyM<String> str){
      return str.map(s->s.toUpperCase());
  }

We could for example pass it a Future that will perform the capitalisation of the String when a data is retrieved from a slow remote service

  toUpperCase(anyM(CompletableFutue.supplyAsync(()-> loadData());

The same code can be used to convert all elements in a Stream to upper case.

  toUpperCase(anyM(Stream.of("hello","world"));

Or to convert the successful result of a method call that may throw an exception to upper case

  toUpperCase(anyM(Try.of(()->loadData()));

Monad operations

We can also use Monads to perform operations on Monads, for example

applyM

We can define a function/s inside a Monad (such as a Stream or Optional) and apply them to another Monad (such as a different Optional or Stream)

 AnyM<Integer> applied =	anyM(Stream.of(1,2,3))
		 							.applyM(anyM(Streamable.of( (Integer a)->a+1 ,(Integer a) -> a*2)));

In this case we will both add 1 to every element in our Stream (1,2,3 ==> 2,3,4) & we will also multiply every element our Stream by 2 (1,2,3 ==> 2,4,6), resulting in a Stream of (2, 2, 3, 4, 4, 6)

Sequences

We can take any Monad and convert it to a sequence. AnyM provides two method types

  • asSequence
  • toSequence

asSequence

asSequence takes the data in the current Monad and transports directly to the new Sequence. So, if we have

 anyM(Optional.of(Arrays.asList(1,2,3)).asSequence()

we get a Sequence that consists of a single item, a list of size 3.

toSequence

On the other hand toSequence performs a Stream flatMap operation that flattens nested collection / or Stream data. So if we perform

anyM(Optional.of(Arrays.asList(1,2,3)).toSequence()

we get a Sequence of three items [1,2,3]

SequenceM

The SequenceM offers a rich array of Stream operators, more than are available in the JDK, and allows easy switching between AnyM and SequenceM types.

Advantages of Generic Monad interfaces

  • Java code can be more generic (where before separate methods may exist for Stream, Optional, CompletableFuture and / or any custom or 3rd party Monads - a single interface can be defined).
  • The Monad interface offers a mechanism to 'lift' non-monads into Monadic form (for example to Stream data from files, urls, Strings or enums - to convert nulls into Optionals, and execute Supplier.get asynchronously)
  • The Monad interface offers mechanisms to flatMap across different monadic types
  • The Monad interface offers at least some opportunity of integrating across the diverse range of libraries being produced for Java today (e.g. FunctionalJava, JAVASLANG, TotallyLazy, HighJ etc)

See also how to implement custom extensions for Cyclops Monad handling

Extensible Generic Monad Operations with AnyM and SequenceM

Example

flatMap (bind) across Stream and Optional types (null entries are removed)

  List<Integer> list = anyM(Stream.of(Arrays.asList(1,3),null))
								.flatMapOptional(d-> Optional.ofNullable(d))
								.map(i->i.size())
								.peek(System.out::println)
								.asSequence()
								.toList();
								
	assertThat(Arrays.asList(2),equalTo(list));

Example

Lift a File to a Stream

With a file "input.file" that contains two lines

  • hello
  • world

We can stream the contents like so...

	List<String> result = anyM("./input.file")
							.liftAndBindFile(File::new)
							.asSequence()
							.toList();
	
	assertThat(result,equalTo(Arrays.asList("hello","world")));

For multiple files...

	List<String> result = anyM("./input.file","./input2.file")
							.liftAndBindFile(File::new)
							.asSequence()
							.toList();
	
	assertThat(result,equalTo(Arrays.asList("hello","world","hello2","world2")));
Clone this wiki locally