Skip to content

Monad & Stream utilities

johnmcclean-aol edited this page Feb 24, 2016 · 15 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

Cyclops Base : Monad and Stream utilities

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.

Monad interface

Rationale

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.

The problem

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).

Cyclops Monad & AnyM

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).

Features in Cyclops Base

  • 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
    

StreamUtils

Multiple simultanous reduction with Monoids

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)

Cycle

StreamUtils.cycle(Stream.of(1,2,3)).limit(6).collect(Collectors.toList())

Results in [1,2,3,1,2,3]

Reverse

StreamUtils.reverse(Stream.of(1,2,3)).collect(Collectors.toList())

Results in [3,2,1]

Stream creation from Iterable and Iterator

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())    

Generic Monad Operations

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)

Closure utils

LazyImmutable

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));

Mutable

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));

Coerce to decomposable / mappable / streamable / functor / monad

Coerce to Map

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;}

}

Coerce to Decomposable

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;}

Type inferencing help

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));

Reverse a Stream

 ReversedIterator.reversedStream(LazySeq.iterate(class1, c->c.getSuperclass())
					.takeWhile(c->c!=Object.class).toList());

Printable interface

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);

Monoids

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.

Clone this wiki locally