Are you not tired of writing those boring for loops, repeating virtually the same boiler plate “finder” or “accumulator” code over and over again? Are you also too afraid of those academic languages or simply enjoy actually getting paid for what you do, i.e., you are confined to Java?
Well, I assume there are tons of higher-order function and algorithm libraries out there, now that templates (oops, I meant “generics”…) have penetrated most Java quarters.
It is actually quite simple to define a high-order function library in Java (JDK 5.0) and create something similar to the Standard Library for C++.
The first thing you have to deal with is how to create applicable entities. In C++ it is easy, just use something than be juxtaposed to “(…)”. Those entities are called functoids. In Java, we have to deal with objects, and we cannot use the “(…)” syntax, i.e., we cannot overload the apply operator. So, we have to pick a method name instead, such as apply. Furthermore, instead of allowing the object to be of any type it is actually beneficial to use a Java interface, such as
-
public interface Fun1<A> {
-
R apply(A arg);
-
}
-
public interface Fun2<R, A1, A2> {
-
R apply(A1 arg1, A2 arg2);
-
}
For functions taking one or two parameters, respectively. An example of a function object complying with our Java functoid notion is then
-
int apply(int m, int n) { return m + n; }
-
}
Now that we have these Java functoids, let us implement algorithms using them.
The simplest – and most common in the beginning of one’s shift to higher-order thinking – is map, which applies a functoid to a sequence of elements, yielding a new sequence. Before attacking the actual map algorithm, we have to decide how to represent sequences in our higher-order library. Well, in C++ they use iterators and we have iterators in Java, right? Well, in fact, we have an even more generic – wile type safe - “iterator” in Java: Iterable.
Unfortunately, the good guys at Sun decided not to make arrays implement this interface
The map algorithm:
-
public static <R, A> void map(
-
Iterable<A> input,
-
List<A> output,
-
Fun1<R, A> fun) {
-
for (A x : input)
-
output.add(fun.apply(x));
-
}
Ok, I cheated a bit, since we output the elements to a list. This is because there are no Output Iterators in Java. So, fo you C++ buffs: this is like using an insert iterator. I could have returned a newly created list, but this way the user can decide the actual type of the output himself. And, no, we do not allow for arrays as direct outputs.
A much more powerful – and often more useful – algorithm is accumulate, which applies a binary operator to a sequence of elements, reducing it to one value (which is why it is often called reduce, or fold in the higher-order world.)
-
public static <T> T accumulate(
-
Fun2<T, T, T> fun,
-
{
-
Iterator<T> iter = input.iterator();
-
if (!iter.hasNext())
-
throw
-
return accumulateAcc(fun, iter, iter.next());
-
}
-
-
public static <T> T accumulateAcc(
-
Iterator<T> input, T accum,
-
Fun2<T, T, T> fun,
-
)
-
{
-
return input.hasNext() ?
-
accumulateAcc(fun, input,
-
fun.apply(accum, input.next) :
-
accum;
-
}
Meta-interestingly enough, the accumulating algorithm needed a helper that actually accumulates the calculated value. Note that the input sequence to accumulate needs to have at least one element. Also note that we restrict ourselves to binary operators on one domain, i.e., both parameters and result belong to the same type.
In order to use the higher-order algorithms we need to place them in a Java class, since there are no free functions in Java. Let us call that class Algo.
Example use:
-
// Square a sequence
-
-
int[] nums = new int[] { 1, 2, 3, 4, 5 };
-
List<Integer> squares =
-
new ArrayList<Integer>();
-
Algo.map(
-
nums,
-
squares,
-
new Fun1<Integer>() {
-
public int apply(int n) {
-
return n * n;
-
}
-
}
-
);
-
-
// Sum the squares
-
-
int sum = Algo.transform(squares,
-
public int apply(int m, int n) {
-
return m + n;
-
}
-
}
-
);
-
+ sum);
Here we created the new Java function objects in place, i.e., using anonymous types. It is a bit sad to see that we need to wrap totally fine functions – such as “+” – but that is what we get for being OO (object-obstinate)…
A last comment: these operators are simple, and numeric, and you should use these higher-order algorithms for domain-specific operators (operating on orders, customers etc.)
I supply the both a test file and the actual implementation of a mini HOF framework below.
Enjoy!
import java.util.*; public class TestHof { public static void main(String[] args) { // Embedding a function in Java is a bit cumbersome List<Integer> myInts = new ArrayList<Integer>(); Hof.transform(Arrays.asList(args), myInts, new Hof.Fun1<Integer, String>() { public Integer apply(String s) { return Integer.parseInt(s); }} ); int sum = Hof.accumulate(myInts, 0, new Hof.Fun2<Integer, Integer, Integer>() { public Integer apply(Integer m, Integer n) { return m+n; } }); int max = Hof.accumulate(myInts, 0, new Hof.Fun2<Integer, Integer, Integer>() { public Integer apply(Integer m, Integer n) { return m > n ? m : n; } }); System.out.println("Sum is " + sum); System.out.println("Max is " + max); } }
Download this code: TestHof.java
import java.util.*; public class Hof { public interface Fun1<R, A> { R apply(A a); } public interface Fun2<R, A1, A2> { R apply(A1 a1, A2 a2); } public static <T1, T2> void transform(Iterable<T1> seq, List<T2> dest, Fun1<T2, T1> fun ) { for (T1 t : seq) dest.add(fun.apply(t)); } public static <T> T accumulate(Iterable<T> seq, T start, Fun2<T, T, T> fun) { T accum = start; for (T t : seq) accum = fun.apply(accum, t); return accum; } }
Download this code: Hof.java
Functional Programming generics higher order Java jdk jdk 5.0