-
Notifications
You must be signed in to change notification settings - Fork 51
Generic Monad Operations
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.

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()));
We can also use Monads to perform operations on Monads, for example
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)
We can take any Monad and convert it to a sequence. AnyM provides two method types
- asSequence
- toSequence
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.
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]
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.
- 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
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));
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")));