28 мар. 2010 г.

FP-approach in Java via Goolge Collections

I'm going to demonstrate some elements of functional programming (fp) approach in java.

For this purpose I use Google Collections Library - provides new and elegant collection types as well as some fp elements and techniques.

A bit more about Google Collections Library:
Google Collections Library FAQ
What is the Google Collections Library?
Introduction to Google Collections by Benjamin Winterberg


For obvious cases I put examples in clojure and in java using fp-approach techniques.

Took short and nice example from "Programming clojure" book - StringUtils.isBlank() is the method (from the Apache Commons) checks to see whether a string is blank: either empty or consisting of only whitespace :
public static boolean isBlank(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if ((Character.isWhitespace(str.charAt(i)) == false)) {
return false;
}
}
return true;
}
Here is a similar implementation in Clojure:
(defn blank? [s] (every? #(Character/isWhitespace %) s))

Java-fp-approach implementation of isBlank method powered by google collections, string is a sequence of chars - that's why I had introduce stringIterator:
import static ru.dolzhenko.lambda.iterators.StringIterator.*;

import static com.google.common.collect.Iterators.*;

public static boolean isBlank(String str) {
return all(stringIterator(str),
new Predicate<Character>() {
@Override
public boolean apply(Character ch) {
return Character.isWhitespace(ch);
}
});
}


map is function mapping, a classic primitive in functional programming - apply a function to all elements of a list, and return the resulting list.


e.g. parse string values to numbers:
(map #(Integer/valueOf %) ["1" "2" "3"])

on java:
Collections2.transform(
ImmutableList.of("1", "2", "3"),
new Function<String, Integer>() {
@Override
public Integer apply(String from) {
return Integer.valueOf(from);
}
});


Transform is also available for Iterables.transform(Iterable, Function), Iterators.transform(Iterator, Function) etc.

The reduce function is a little less obvious in its intent. This function reduces a list to a single value by combining elements via a supplied function.


The filter function returns elements of collection that satisfy predicate.


For instance, Let's solve ProjectEuler - Problem #1 - find the sum of all the multiples of 3 or 5 below 1000:
(reduce + (filter #(or (zero? (mod % 3)) (zero? (mod % 5))) (range 1000)))

... and the same on java:
// import static import static com.google.common.base.Predicates.*;
// import static com.google.common.collect.Iterators.*;

// import static ru.dolzhenko.lambda.iterators.LambdaIterators.*;
// import static ru.dolzhenko.lambda.functions.LambdaFunctions.*;
// import static ru.dolzhenko.lambda.functions.LambdaPredicates.*;

final BigDecimal value = reduce(sum(), filter(range(1000),
or(
zero(mod(new BigDecimal(3))),
zero(mod(new BigDecimal(5)))
)));


Yeah, there are no reduce and range functions in google collections too, so I had to build my own theme park, with blackjack make my own collection of functions LambdaIterators & Co. that supplement google collections with such useful functions as reduce, range, take, takeWhile, iterate, first, rest.

Definitely, Predicate is a function, so it can be described as
public interface Predicate<T> extends Function<T, Boolean>{ 
}
To avoid autoboxing overhead authors of google collections prefer to introduce new interface.

After that we able to construct expressions like
(take 5 (filter even? (iterate inc 1)))
on java:
take(5, filter(iterate(inc(), BigDecimal.ONE), even()))
as well as for finite collection as well as for infinite collection - functions
filter, transform, take, takeWhile, iterate, first, rest are lazy - in other words, elements are not calculated until they are needed.

btw. lambdaj is useful framework that provides pseudo fp approach in java.

4 комментария:

.sid комментирует...

Don't you think that java do not fit very well with functional abstractions?

Well, of course you can use google collections, predicate and all that stuff, but when host language do not have syntax support for first class functions it's very complicated. I'd rather using for loops and not to expose all this predicates in my API.

But I'm not agains functional approach. I'm love it (and also participiating in Project.Euler), but not in java :)

Nevertheless, thanks for the blog entry...

Владимир Долженко комментирует...

@.sid
sure - java is not functional language, but we able to use some elements of fp techniques.

I guess closures (in jdk 7?) can change situation a bit.

.sid комментирует...

Yeah, I think so. I have a lot of places where closures will provide more fluent and simple API.

There is one problem which will remain. As far as I know, standard collection library will not use closures for backward compatibility reasons. It means that closure friendly code will not set up in near future. Googe collections is good library, but it's not a standard library. I think you understand what a mean.

Владимир Долженко комментирует...

There are lot of aches due to java backward-compatibility.
eg. primitive classes are available: int.class, long.class etc - BUT!
It is impossible to use these classes in generics.

refer back to jdk and 3rd party libraries - apache libraries are gentlemen set de-facto.

A lot of enhancements would be nice to see in jdk, but...