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

There are 3, very useful and powerful monads in JDK 8 : Stream, Optional and CompletableFuture. Cyclops provides a generic abstraction layer over them all (and any other Monads you find or create - included in Cyclops are additional monad types from Cyclops itself, and Comprehenders for Javaslang Monads).
This allows you to do things, like aggregate across types
List<Integer> result = anyM(Optional.of(Arrays.asList(1,2,3,4)))
.<Integer>aggregate(anyM(CompletableFuture.completedFuture(5)))
.toList();
assertThat(result,equalTo(Arrays.asList(1,2,3,4,5)));
Or replicate data in an Optional or a Stream
AnyM<List<Integer>> applied =anyM(Optional.of(2)).replicateM(5);
assertThat(applied.unwrap(),equalTo(Optional.of(Arrays.asList(2,2,2,2,2))));
AnyM<Integer> applied =anyM(Stream.of(2,3,4)).replicateM(5);
assertThat(applied.toList(),equalTo(Arrays.asList(2,3,4,2,3,4,2,3,4,2,3,4,2,3,4)));
To run multiple different filtering or function application operations together
AnyM<Stream<Integer>> applied =anyM(Stream.of(1,2,3))
.filterM(anyM(Streamable.of( (Integer a)->a>5 ,(Integer a) -> a<3)));
assertThat(applied.map(s->s.collect(Collectors.toList())).asSequence().toList(),
equalTo(Arrays.asList(Arrays.asList(1), Arrays.asList(2),Arrays.asList())));
AnyM<Integer> applied =anyM(Stream.of(1,2,3))
.applyM(anyM(Streamable.of( (Integer a)->a+1 ,(Integer a) -> a*2)));
assertThat(applied.toList(),equalTo(Arrays.asList(2, 2, 3, 4, 4, 6)));
Including multiple reduction and collection operations
List result = anyM(Stream.of(1,2,3)).asSequence().collect(Stream.of(Collectors.toList(),
Collectors.summingInt(Integer::intValue),
Collectors.averagingInt(Integer::intValue)));
assertThat(result.get(0),equalTo(Arrays.asList(1,2,3)));
assertThat(result.get(1),equalTo(6));
assertThat(result.get(2),equalTo(2.0));
Monoid<Integer> sum = Monoid.of(0,(a,b)->a+b);
Monoid<Integer> mult = Monoid.of(1,(a,b)->a*b);
val result = anyM(Stream.of(1,2,3,4)).asSequence().
reduce(Stream.of(sum,mult) );
assertThat(result,equalTo(Arrays.asList(10,24)));
Apart from that, and a whole lot more similar operators, it allows you to write much more generic code, that can operate on any type of Monad.
Stream, Optional and CompletableFuture behave as Monads in Java 8, but the JDK doesn't provide a Monad interface - which impacts the resuability of code which performs Monad operations (e.g. flatMap / map). In Java 8 we write specific code for Optional or Stream types. Which is a pity, as Optional and Stream are similar concepts (Optional represents either None or Some (0 or 1 value) where as a Stream can have No elements, One elements or Many. There are clearly cases where we could write generic code that could recieve either a Stream or an Optional, but we can't (yet). With a small number of Monadic types in Java, we can always do conversions each time, but that potential places limits on the ability of the community to grow the number of types and maximise flexibility while the do so.
Implementing an actual universally usable interface in Java for functional types such as Functor, Monoid, Semigroup, Monad etc. is pretty damn hard, if not impossible (because you can't put generics on generics / lack of higher kinded types). Implementing interfaces that take a type, which in turn takes another type (and so on) is tough (see HighJ or v1.2.2 of JAVASLANG for very good attempts at this).
The Cyclops Monad interface isn't intended to be used as a standard interface. In fact, it's purpose is to 'coerce' types that behave as Monads to that interface and is used internally by Cyclops to implement the simpler AnyM class. In other words, it can be used to wrap a Stream, Optional or CompletableFuture - and allow generic code that can thus operate over any of those types. It will attempt to coerce other types via a combination of InvokeDynamic byte code (and making some assumptions about method names), and dynamic proxies to convert between JDK functional interfaces if necessary. Cyclops has been written in such a way that it is possible to auto-discover new Comprehenders (the mechanism Cyclops uses to 'understand' the behaviour of a given Monadic class).
Which unfortunately means we can't abstract behaviour across Streams, Optionals, CompletableFutures and other Monad types. Cyclops introduces a Monad interface and a mechanism to natively (or via InvokeDynamic) coerce an Object to that interface. This means we can write generic functions / methods that accept a Monad - and compose map / flatMap / filter / reduce operations against the interface. The implementation could be a Stream, Optional, CompletableFuture, Try, Switch, Case, Either (etc).
-
Generic Monad operations
-
Specific and InvokeDynamic based Monadic comprehension (for use in cyclops-for-comprehension and elsewhere)
-
Mutable / LazyImmutable Utils for working with Closures / captured values & variables
-
Stream utils - e.g. reverse a stream
-
Interfaces
Streamable : repeatable stream() Decomposable (for Value objects) : unapply() Mappable (convert fields to Map) : toMap() Functor (Generic functor interface) : map(Function) Monad (Generic Monad interface) : flatMap(Function) Monoid zero(), combiner(), reduce(Stream s) Gettable : get()
-
Coerce / wrap to interface
asStreamable asDecomposable asMappable asFunctor asGenericMonad asGenericMonoid asSupplier
Monoid<String> concat = Monoid.of("",(a,b)->a+b);
Monoid<String> join = Monoid.of("",(a,b)->a+","+b);
StreamUtils.reduce(Stream.of("hello", "world", "woo!"),Stream.of(concat,join));
Results in ["helloworldwoo!",",hello,world,woo!"]
See also Monoid.reduce(Stream s)
StreamUtils.cycle(Stream.of(1,2,3)).limit(6).collect(Collectors.toList())
Results in [1,2,3,1,2,3]
StreamUtils.reverse(Stream.of(1,2,3)).collect(Collectors.toList())
Results in [3,2,1]
From Iterable
StreamUtils.stream(Arrays.asList(1,2,3)).collect(Collectors.toList())
From Iterator
StreamUtils.stream(Arrays.asList(1,2,3).iterator()).collect(Collectors.toList())
Wrap and nest any Monadic type :
val list = MonadWrapper.<List<Integer>,Stream>of(Stream.of(Arrays.asList(1,3)))
.bind(Optional::of).<Stream<List<Integer>>>unwrap()
.map(i->i.size())
.peek(System.out::println)
.collect(Collectors.toList());
assertThat(Arrays.asList(2),equalTo(list));
bind :-> flatMap
Not possible to flatMap an Optional inside a Stream in JDK, but you can with the MonadWrapper (or any other type of Monad)
Set values once only inside a Closure.
LazyImmutable<Integer> value = new LazyImmutable<>();
Supplier s= () -> value.getOrSet(()->10);
assertThat(s.get(),is(10));
assertThat(value.getOrSet(()->20),is(10));
Fully mutable variable wrapper manipulatable inside a closure
import static com.aol.cyclops.lambda.utils.Lambda.*;
Mutable<Integer> myInt = new Mutable<>(0);
λ2((Integer i)-> (Integer j)-> myInt.set(i*j)).apply(10).apply(20);
assertThat(myInt.get(),
is(200));
um.. λ2 ? (Type inferencing helper :) - and without it
Mutable<Integer> myInt = Mutable.of(0);
BiFunction<Integer,Integer,ClosedVar<Integer>> fn = (i,j)-> myInt.set(i*j);
fn.apply(10,20);
assertThat(myInt.get(),
is(200));
This offers and alternative to adding getters to methods solely for making state available in unit tests.
Rather than break production level encapsulation, in your tests coerce your producition object to a Map and access the fields that way.
@Test
public void testMap(){
Map<String,?> map = AsMappable.asMappable(new MyEntity(10,"hello")).toMap();
System.out.println(map);
assertThat(map.get("num"),equalTo(10));
assertThat(map.get("str"),equalTo("hello"));
}
@Value static class MyEntity { int num; String str;}
}
The Decomposable interface specifies an unapply method (with a default implementation) that decomposes an Object into it's elemental parts. It used used in both Cyclops Pattern Matching (for recursively matching against Case classes) and Cyclops for comprehensions (where Decomposables can be lifted to Streams automatically on input - if desired).
@Test
public void test() {
assertThat(AsDecomposable.asDecomposable(new MyCase("key",10))
.unapply(),equalTo(Arrays.asList("key",10)));
}
@Value
static class MyCase { String key; int value;}
import static com.aol.cyclops.functions.Lambda.*;
Mutable<Integer> myInt = Mutable.of(0);
λ2((Integer i)-> (Integer j)-> myInt.set(i*j)).apply(10).apply(20);
assertThat(myInt.get(),
is(200));
ReversedIterator.reversedStream(LazySeq.iterate(class1, c->c.getSuperclass())
.takeWhile(c->c!=Object.class).toList());
Implement Printable to add the following method
T print(T object)
Which can be used inside functions to neatly display the current value, when troubleshooting functional code e.g.
Function<Integer,Integer> fn = a -> print(a+10);
Fit the Stream.reduce signature. Can be used to wrap any Monoid type (e.g. functional java).
Monoid.of("",(a,b)->a+b).reduce(Stream.of("a","b","c"));
Produces "abc"
fj.Monoid m = fj.Monoid.monoid((Integer a) -> (Integer b) -> a+b,0);
Monoid<Integer> sum = As.asMonoid(m);
assertThat(sum.reduce(Stream.of(1,2,3)),equalTo(6));
Use in conjunction with Power Tuples or StreamUtils for Multiple simultanous reduction on a Stream.