Candy is a collection of java functions intended to simplify code structure, by reducing the need of variables. It introduce tail calls using a trampoline, functions for composition of lazy iterables and functions for reversing functional composition. Candy is supposed to be small, simple and easy to use. It relies on Java 8 and the lambda expression.
It is early and interface may still change
Candy can be found in the central maven repo, just add the following to your pom.xml, or similar depending on your build, and maven should download it for you.
<dependencies>
<dependency>
<groupId>com.github.stefanvstein</groupId>
<artifactId>candy</artifactId>
<version>0.1</version>
</dependency>
</dependencies>
Below are a few example usages of some functions to describe the basics. The functions are grouped in classes.
Choices.java have abstractions of the if statement as functional expressions
Maybe.java have functions for using Optional
Iterables.java have functions for lazy and composable Iterables
Threading and Functions.java have functions for transforming function calls
Trampoline.java have functions for implementing recursive tail calls
Choices.java
Choices contains ifelse, when, unless, mapOr, cond & either
Choices is a number of expression replacements for the if-statement. It is used to reduce the control flow graph and to make code easier to read by naming different types of conditional expressions
The ifelse
function is the expression equivalent of the if else
statement. It represents a single value and eliminates the need of temporary variables.
// Write:
int question=3;
final String answer=ifelse(question==3,
()->"YES",
()->"NO"));
println(answer);
// instead of either:
int question=3;
String answer="Unknown";
if(question==3)
answer="YES";
else
answer="NO";
println(answer);
// where the temporary variable, answer, is needed or:
int question=3;
if(question==3)
println("YES");
else
println("NO");
// where println calls are duplicated.
Code becomes less complex with ifelse
expressions. Nested ifelse
expressions logically becomes much simpler, as complexity doesn't grow exponentially. Expressions doesn't require temporaries and represent a single value even when nested. ifelse
will also remove your {}
from your lambdas.
ifelse
expressions are equivalent to the ternary operator, which also is expression with a value, but having verb first can be easier to read as expressions grow. E.g.:
int question=3;
final String answer = question==3 ? "YES" : "NO";
println(answer);
Conditional expressions as functions, like ifelse
, can be categorized to further reveal intent. The function when
is like ifelse
but returns null
instead of evaluation of an else
clause.
final String answer = when(question==3, ()->"YES"));
// is same as:
final String answer = question==3 ? "YES" : null;
With the when
function you know you don't need to look further for an else
clause. The intent is revealed at head, in contrast to the if
statement where you need to parse ahead to understand structure and occasionally find an else
clause. This is difficult with nested if else
statement structures.
if
statements without corresponding else
clauses usually indicate side effects. Side effects should not represent a value. The purpose of the void
keyword is to indicate side effects, routines that do not return anything.
doWhen
acts like when but does not return anything. It reveals that it is used to do side effects when a condition is met, and that your code should not act on the outcome, it's a side effect, unless something exceptional occur.
doWhen(debug, ()->println("DEBUG - we have reached all the way to this point"));
When the normal path through an if statement is the truth, when the else clause is exceptional, it becomes easier to understand code when you read the then clause before the test. The test and the else clause becomes a guard for exceptional conditions. With the unless function the important path stands out by standing ahead.
return unless( ()-> "There are " + n + " apples",
n<0,
()-> "Weird!! Less than 0 apples?");
// is both easier understand, as the normal case comes first, and less complex than:
if(n<0)
return "Weird!! Less than 0 apples?"
else
return "There are " + n + " apples"
It is common that you have to check if an object exist prior to calling it. Sometimes it is difficult to see the value of the absence of the call due to null.
return mapOr(a, String::toUpperCase, ()->"");
//..is simpler than:
if (null==a)
return "";
else return a.toUpperCase();
Using Optional is another mean to handle null, mapOr is sometimes easier as Optional as it is a single call.
The cond
function is kind of a switch case
expression. Each case is a pair of Suppliers where the first is a Boolean Supplier and the other is of the cond expressions return type. A last stand alone Supplier is the default, if all other cases has returned FALSE. As each case is a function call it can match on anything. cond
will short circuit when a match is found
String a= cond(()->1==2, ()->"What?",
()->1==1, ()->"Yes",
()->2==2, ()->"But 1==1!",
()->"catch all");
The cond function is helpful in reducing nested if statements
Either is the pure if else expression. The test is performed on an object using a predicate of the objects type, which result application of either Suppliers.
String s= either(value,
this::is3,
v->"III",
Integer::toBinaryString);
//would be the equivalent of:
String s = null;
if(is3(value))
s="III";
else
s=Integer.toBinaryString(value)
either
reduces the need of variables further.
Maybe.java
To Usage
Null checks can easily be eliminated with Optional. Sometimes its preferable to have the verb first, as Optional is a object oriented construct. It's a matter of taste. Verb first programs may be easier to maintain.
Maybe.map(String::toUpperCase, Maybe.just("Value"))
// would yield Maybe.just("VALUE") while
Maybe.map(String::toUpperCase, Maybe.nothing())
//..yield Maybe.nothing().
Maybe.just and Maybe.nothing creates Optionals while Maybe.map calls map on that Optional.
There is also a Maybe.maybe(T t) function that return Maybe.nothing() if t happen to be null.
Iterables.java
Back to Usage
Iterables contains: map, filter, reduce, take & iterate
Streams in Java are great as they provide the functional construct we are used to in other languages. Since streams are an object oriented construct it is pretty hard to add new functions. Iterables is a set of functions with similar functionality, but without the OO principles. It is used to create transforming lazy and composable Iterables. Iterables are simpler, easier to extend, and have guaranteed ordering. All computation is done with the iterators extracted from these Iterables.
Returns an Iterable which iterator applies a function to each element
Iterable<Integer> inced = Iterables.map( e -> e+1, Arrays.asList(1, 2, 3));
println(Iterables.list(inced));
would print [2, 3, 4]
The iterator produced, when converting the Iterable to list, is lazy and applies the function as the next value is retrieved. The list function produces a List from the next iterator of a Iterables, unless the Iterable isn't already a list.
Filter, as usually, removes elements not matching a predicate
Iterable<Integer> even = Iterables.filter( e -> 0==e%2, Arrays.asList(1, 2, 3, 4));
println(Iterables.list(even));
would print [2, 4], as 1 and 2 are odd numbers
The iterators produced by even are lazy, which is nice since we then can compose the functions without excessive memory allocation.
Iterable<Integer> odd = Iterables.map( v-> v+1,
Iterables.filter( e -> 0==e%2,
Arrays.asList(1, 2, 3, 4)));
println(Iterables.list(odd));
would print [3, 5]
With some static imports it becomes more readable
Function<Integer, Integer> inc = (v) -> v+1;
Predicate<Integer> isEven = (e) -> 0==e%2;
void printOdd(){
Iterable<Integer> odd = map( inc,
filter( isEven,
asList(1, 2, 3, 4)));
println(list(odd));
}
Reduce or fold left as we may call it in other languages is terminal, like list, and does not necessarily return an Iterable. It combines elements of the next iterator into a single result by repeated application of a combining operation. As a simple example we could sum like:
int sum = reduce((a,v) -> a+v, 0, asList(1,2,3,4))
println(sum);
would print 10
This is terminal
Rather than producing a single value like you do with reduce, you may need the first few elements in iterators. The take functions creates Iterables producing iterators that limit the number of elements produced.
Iterable<Integer> afew = take(3, asList(1,2,3,4));
println(list(afew));
would print [1, 2, 3] while takeWhile takes elements as long as they match a predicate.
Iterable<Integer> afew = takeWhile(e -> e < 3, asList(1,2,3,4));
println(list(afew));
would print [1, 2].
The iterate function takes a function and a initial value and produces iterators that produces repeated application. That is initial, function(initial), function(function(initial)) and so on.
The infinite rangeFrom function could be created as:
Iterable<Integer> rangeFrom(int initial){
return iterate(v->v+1, initial);
}
Since rangeFrom would continue forever we can limit the iterators produced using take.
println(list(take(4, rangeFrom(1))));
would print [1,2,3,4]. rangeFrom would not produce more than 4 numbers, as take won't pull more numbers.
There is more:
- first takes the first element of the next iterator. This is terminal
- second takes the second element of the next iterator. This is terminal
- nth takes the nth element of the next iterator. This is terminal
- last takes the last element of the next iterator
- rest is the iterable with all but the first element
- next is rest but never empty
- with is the iterable with an element prepended
- withLast is the iterable with an element appended
- drop is the iterablw with a number of elements dropped
- dropLast has all elements except the last
- dropWhile is the iterable of with all first matching a predicate removed
- flatMap is the iterable of concatenations of the result of applying map to every element in iterators of data
- reductions is the iterable of producing intermediate values of the reduction of elements by f, starting with accumulator
- takeNth is the iterable of every nth element
- partition is the iterable of num length iterables
- partitionBy is the iterable of iterables grouped by application
- splitAt is the tuple of iterables split at i, where both are lazy
- repeatedly is the iterable of continuous supplier retrieval
- repeat is the iterable of continuous value
- range is the iterable of increasing integers
- cycle is the iterable of repeated iterable
- stream is a iterable as stream
- iterable is a stream of iterable
- set is a set of the next iterators values, unless the supplied iterable already is a set. This is terminal
- forceList is a new list of iterable. This is terminal
- forseSet is a new set of iterable. This is terminal
Again, the iterators produced are lazy and the while functions can safely be composed with other Iterables functions. It is about what to transform rather than how to iterate data.
Back to Usage
As seen above functional composition can be a little difficult to read. It executes backwards. With the thread function you can write nested composition in serial fashion, or backwards if you will.
println(thread(rangeFrom(1),
r->take(4, r),
Iterables::list));
// is same as:
println(list(take(4, rangeFrom(1))));
The thread function takes the first value, and applies that to the first function, and applies the result of that to the second function and finally returns the result of the last function.
When we have more than one argument, we can clean things up by resorting to partial application. Functions.java contains functions that improve functions. That is e.g. the partial application of converting a BiFunction to a Function by applying the first argument prior to executing the function.
println(thread(rangeFrom(1),
function(Iterables::take, 4),
Iterables::list));
Since the if statements replacements found in Choices.java are expressions, they also fit well within the thread function.
Trampoline.java
Back to Usage
Functional programming languages usually utilize recursive tail calls to implement iteration. It is simple and allow for programming without side effects as variables are turned into final function arguments. Java does not support optimized recursive calls and will continuously consume stack. Both compilers and libraries can optimize recursive tail calls using a trampoline.
A function like:
static String foo(int a) {
return
ifelse(a > 200000,
() -> "Foo"
() -> foo(a + 1));
}
...that eats stack, can be rewritten with a new return type, Supplier<Continuation<?>>
and explicit intent on recursion using done
and recur
:
static Supplier<Continuation<String>> foo(int a) {
return () ->
ifelse(a > 200000,
() -> done("Foo"),
() -> recur(foo( a + 1)));
}
...and be used by a trampoline
to recur without eating stack:
trampoline(foo(0));
The functions done
and recur
creates Continuations telling the trampoline to either return with a supplied value, "Foo", or to continue recurring with the data passed to recur.
This technique does allow for mutual recursion, like:
static Supplier<Continuation<Boolean>> isEven(int a) {
return () ->
ifelse(a == 0,
() -> done(true),
() -> recur(isOdd(a - 1)));
}
static Supplier<Continuation<Boolean>> isOdd(int a) {
return () ->
ifelse(a == 0,
() -> done(false)
() -> recur(isEven(a - 1)));
}
trampoline(isEven(200000));
The Continuation either contains a final value or another function returning a Continuation. The trampoline will call the repeatedly returned continuation function as long as they are returned. When the value of done
is returned the eventually contained final value is returned. The functions done
and recur
produce the appropriate Continuations, with final value or continuation.
Three other functions lazy
, seq
, and stop
are used similarly to produce lazy recursion through a the Iterator
interface. The lazy
function returns a lazy Iterator that generate successive values using a trampoline, the second value of seq
.
A lazy higher order map function can be implemented as:
public static <A, V> Iterable<V> map(final Function<? super A, V> f,
final Iterable<A> data) {
requireNonNull(f);
return when(nonNull(data), () ->
() -> either(data.iterator(),
Iterator::hasNext,
i -> lazy(mapI(f, i)).iterator(),
i -> emptyIterator()));
}
private static <A, V> Supplier<Continuation<V>> mapI(final Function<? super A, ? extends V> f,
final Iterator<A> elements) {
return () -> either(f.apply(elements.next()),
v -> elements.hasNext(),
v -> seq(mapI(f, elements), v),
Trampoline::done);
}
The stop
function will terminate the iterator, while recur
will continue recursion without delivering a value to the iterator. The value of calling recur
will be invisible to the iterator.
New lazy iterables can easily be constructed by composing the dozen of functions already existing in e.g. Iterable.java. Perhaps like:
<T> Iterable<T> interleave(Supplier<? extends T> t,
Iterable<? extends T> d){
return with(first(d),
flatMap(v-> asList(t.get(), v),
Iterables.next(d)));
}
Known functional algorithms with tail recursion can be implemented in Java the way they are defined.