-
Notifications
You must be signed in to change notification settings - Fork 51
Utilities for working with Java 8 Functions
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.
Utilities for working with Lambda expressions that capture values or variables in the enclosing scope.
- Mutable
- MutableInt
- MutableDouble
- MutableLong
- MutableBoolean
- MutableByte
- MutableShort
- MutableChar
- MutableFloat
- LazyImmutable
Java lambda expressions can access local variables, but the Java compiler will enforce an 'effectively' final rule. cyclops-closures makes capturing variables in a mutable form a little simpler.
Store a single Object or primitive that can be accessed via get, set via set or mutated via mutate.
MutableInt num = MutableInt.of(20);
Stream.of(1,2,3,4)
.map(i->i*10)
.peek(i-> num.mutate(n->n+i))
.forEach(System.out::println);
assertThat(num.get(),is(120));
Not suitable for multi-threaded use, see AtomicReference, AtomicIntger, AtomicDouble & AtomicLong for more appropriate alternatives for sharing data across threads.
A set-once wrapper over an AtomicReference. Unlike the MutableXXX classes LazyImmutable is designed for sharing across threads where the first thread to attempt can write to the reference, and subsequent threads can read only.
Create a memoizing (caching) Supplier that can be shared across threads.
public static <T> Supplier<T> memoizeSupplier(Supplier<T> s){
LazyImmutable<T> lazy = LazyImmutable.def();
return () -> lazy.computeIfAbsent(s);
}
Light weight module (no dependencies) which adds the following features for Java 8 Functions, Consumers and Suppliers
- Caching / Memoization
- Currying
- Partial Application
- Type inference
Use static Memoize methods on Memoize utility class to create a cachable reference to a function, suppler or method. Provide custom cache interfaces via the Cachable functional interface
Cyclops can convert any function (with up to 8 inputs) or method reference into a chain of one method functions (Currying). This technique is a useful (and more safe) alternative to Closures. The Curried function can be created and values explicitly passed in rather than captured by the compiler (where-upon they may change).
import static com.aol.cyclops.functions.Curry.*;
assertThat(curry2(this::mult).apply(3).apply(2),equalTo(6));
public Integer mult(Integer a,Integer b){
return a*b;
}
assertThat(Curry.curry2((Integer i, Integer j) -> "" + (i+j) + "hello").apply(1).apply(2),equalTo("3hello"));
assertThat(Uncurry.uncurry3((Integer a)->(Integer b)->(Integer c)->a+b+c)
.apply(1,2,3),equalTo(6));
import static com.aol.cyclops.functions.Lambda.*;
Mutable<Integer> myInt = Mutable.of(0);
Lambda.l2((Integer i)-> (Integer j)-> myInt.set(i*j)).apply(10).apply(20);
assertThat(myInt.get(),
is(200));
CurryConsumer.curry4( (Integer a, Integer b, Integer c,Integer d) -> value = a+b+c+d).apply(2).apply(1).apply(2).accept(3);
assertThat(value,equalTo(8));
UncurryConsumer.uncurry2((Integer a)->(Integer b) -> value = a+b ).accept(2,3);
assertThat(value,equalTo(5));
(Or even wrapping them inside RuntimeException)
public Data load(String input) {
try{
}catch(IOException e) {
throw ExceptionSoftener.throwSoftenedException(e);
}
In the above example IOException can be thrown by load, but it doesn't need to declare it.
Where we have existing methods that throw softened Exceptions we can capture a standard Java 8 Functional Interface that makes the call and throws a a softened exception
Function<String,Data> loader = ExceptionSoftener.softenFunction(file->load(file));
public Data load(String file) throws IOException{
///load data
}
Stream.of("file1","file2","file3") .map(ExceptionSoftener.softenFunction(file->load(file))) .forEach(this::save)
We can simplify further with method references.
Data loaded = ExceptionSoftener.softenFunction(this::load).apply(fileName);
Stream.of("file1","file2","file3")
.map(ExceptionSoftener.softenFunction(this::load))
.forEach(this::save)
- Mappable
- Printable
- Gettable
Mappable allows any Object to be converted to a map. Printable adds print(String) to System.out functionality Gettable allows any Object to be converted to a Supplier
-
Coerce / wrap to interface
asStreamable asDecomposable asMappable asFunctor asGenericMonad asGenericMonoid asSupplier
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;}
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);
- Cumulative Validation
Accumulate
ValidationResults results = CumulativeValidator.of((User user)->user.age>18, "too young", "age ok")
.isValid(user->user.email!=null, "user email null","email ok")
.accumulate(new User(10,"[email protected]"));
assertThat(results.getResults().size(),equalTo(2));
Accumulate until fail
ValidationResults<String,String> results = CumulativeValidator.of((User user)->user.age>18, "too young", "age ok")
.add(Validator.of((User user)->user.email!=null, "user email null","email ok"))
.accumulateUntilFail(new User(10,"[email protected]"));
assertThat(results.getResults().size(),equalTo(1));