Skip to content
johnmcclean-aol edited this page Nov 22, 2016 · 9 revisions

AnyM is a special type of functor in cyclops-react. It is a functor that abstracts over monads (and has two monadic sub-types AnyMValue and AnyMSeq).

AnyM Class Hierarchy

class hierarchy

Overview

AnyM is an interface that can wrap any monad type. This allows us to write common code that can accept an Optional, CompletableFuture, Stream or List from the JDK as well as any of the cyclops-react datatypes (extended collections, LazyFutureStreams, FutureW, Try, Xor, Ior, FeatureToggle, Reader or Maybe). Via the cyclops integration modules we can also abstract over types from Javaslang, Functional Java, RxJava, Reactor and Guava.

Values and sequences

We split the AnyM type into 2 sub-types AnyMValue and AnyMSeq. AnyMValue represents any monad that ultimately resolve to a single value (such as Maybe / Optional / Either / Try / Future / Reader) and AnyMSeq represents any monad that ultimately resolves to a sequence of values.

AnyM itself is not a monad, we can't safely represent the flatMap method on it, but it's two sub-types are. Both AnyMValue and AnyMSeq implement flatMap.

Value Example

Use AnyM to combine Optionality (Optional) and Exception handling (Try). AnyM can be used to augment functionality missing from the original implementation (for example Applicative like combining in java.util.Optional or monadic flatMapping in guava's Optional).

AnyMValue<Integer> tryMonad = AnyM.fromTry(Try.success(20));
AnyMValue<Integer> optMonad = AnyM.fromOptional(Optional.of(10));
  • using flatMap (monad methods)
intMonad.flatMap(i->tryMonad.map(s->s+i))
        .map(i->i*2);

//AnyMValue[60]
  • using combine (Applicative Functor methods)
intMonad.combine(tryMonad,s->(s+i)*2);
       
//AnyMValue[60]

Seq Example

We can use AnyM to inject the ability to schedule emission from a Functional Java (or Javaslang Stream), for example

import static com.aol.cyclops.functionaljava.FJ.stream;


stream(Stream.stream(1,2,3)).schedule("* * * * * ?", Executors.newScheduledThreadPool(1))
                            .connect()
                            .forEach(System.out::println)

The reason for the separation

AnyM types need to accept AnyM as a return type from the transforming function in a flatMap operation. For monads that store a single value this means making a decision about what data to accept from non-scalar monads (monads that represent a sequence of values). Truncating the sequence may cause some runtime surprises for users, so we separated AnyM into AnyMValue and AnyMSeq.

AnyM::flatMapFirst

If you wish to abstract across both Values and Sequences the method flatMapFirst method will only accept the first element from a sequence when performing a flattening transform into a value. E.g.

AnyM.fromOptional(Optional.of(10))
    .flatMapFirst(i->AnyM.fromStream(ReactiveSeq.range(0,i));

//AnyM[0]

AnyM.fromStream(Stream.of(10))
    .flatMapFirst(i->AnyM.fromStream(ReactiveSeq.range(0,i));

//AnyM[0..10]

Converting from AnyM to AnyMValue / AnyMSeq

The visit method on AnyM allows users to safely determine whether this AnyM type is a value or a seq (without instanceofing or casting) e.g.

String type = AnyM.fromOptional(Optional.of(10))
                  .visit(v->"value",s->"seq");

//"value"

String type = AnyM.fromStream(Stream.of(10))
                  .visit(v->"value",s->"seq");

//"seq"

Converting to other types

  1. AnyM#unwrap will return the wrapped monad, in general this should only be used locally within the same method that the AnyM is created so we can be sure of it's type.
  2. AnyMValue#toXXXX, AnyMSeq#toXXX there are a large range of conversion operators available on the AnyMValue and AnyMSeq types that can convert AnyM's to JDK or cyclops-react monadic types
  3. AnyM#collect JDK 8 collectors can be used to convert an AnyM from one type to another
  4. AnyM#to The to method allows both custom operators and custom converters to be used to convert an AnyM to another type

Folding / Reduction

AnyM types also have a full range of fold / reduce operators available, which means that data can often be extracted in useful form without conversion back to an unwrapped monadic form.

E.g. to sum all the values in any Sequence type we can write a generic method like so ->

public int sumValues(AnyMSeq<Integer> sequence){
     return sequence.reduce(Monoids.intSum);
}  

Our sumValues method can now accept JDK Lists, Sets, Queues, Streams, cyclops-react extended types, Reactor Flux, RxJava Observable, Guava FluentIterables, Functional Java Lists and Streams as well as Javaslang Array, Lists and Vectors.

Reactive Streams

AnyM extends Publisher so other reactive Streams implementations can subscribe to our AnyM type.

import static com.aol.cyclops.javaslang.Javaslang.traversable;
import javaslang.collection.Stream;

Stream<Integer> stream = Stream.of(1,2,3)
AnyMSeq<Integer> seq= traversable(stream);

//process seq using some generic code

Flux<Integer> flux = Flux.from(seq); //subscribe to Javaslang Stream using a Reactor Flux

forEach, forEachWithErrors

Similarly we can provide a consumer to listen to each event generated by the wrapped monadic type as we iterate over it.

AnyM<Integer> value = Javaslang.value(Option.some(10));
AnyMSeq<Integer> seq = AnyM.fromStream(ReactiveSeq.of(10,20,30));

value.forEach(System.out::println);
//10

seq.forEach(System.out::println);
//10
//20
/30
  

Further reading

Sebastian Milles has put together a blog entry where he converts an advanced algorithm from Haskell to Java using cyclops-react AnyM.

Deterministic and Non-Deterministic Finite State Machines with Cyclops

Clone this wiki locally