Yardena has posted 2 posts at DZone. You can read more from them at their website. View Full User Profile

Functional Programming: Predicate + Function = Rule

05.01.2008
| 7213 views |
  • submit to reddit

I want to share with you a little gadget that uses google-collections. Basically, a rule is a condition and an action.
So I will use Predicate as a condition and Function as an action. The composite function then consists of list of rules, for the first predicate that evaluates to true, it will invoke the corresponding function. If none of the predicates are matched - default function will be invoked.

For example, here is how I define a function "size" of an arbitrary object:

Function<Object, Integer> size = new Rules<Object,Integer>().
addRule(isNull(), constant(0)).
addRule(
or(
instanceOf(Collection.class),
instanceOf(Map.class)),
self().reflect("size").cast(Integer.class)).
addRule(
asPredicate(self().reflect("getClass").
reflect("isArray").cast(Boolean.class)),
self().reflect(Array.class, "getLength").cast(Integer.class)).
setDefault(constant(1));
If you read my earlier post, you already know about the FunctionChain, this is where I statically import self() from. The reflect() function transfers Object to Object by invoking a parameterless method with given name on the current object, or passes current object to a single-parameter method of another object (or class for static methods). The cast() method performs a cast to given class. The instanceOf() function is a shortcut to ClassPredicate which returns true if the class it holds is assignable from a given class. I also assume statically imported or and isNull Predicates and Functions.constant. (I could replace reflect function calls with a special size and length functions, but I think it's enough to illustrate the idea as it is.)

The same segment in "normal" coding would look something like that:
if (object == null) {
return 0;
} else if (object instanceof Collection) {
return ((Collection)object).size(); {
} else if (object instanceof Map) {
return ((Map)object).size(); {
} else if (object.getClass().isArray()) {
return Array.getLength(object);
} else {
return 1;
}

I don't pretend one is better than the other, but it's interesting enough, I think, and sometimes useful.

Here is the source code for Rules class:

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import java.util.Collection;
import java.util.LinkedList;

public class Rules<F,T> extends LinkedList<Rules.Rule<F,T>> implements Function<F,T> {
private Function<? super F, ? extends T> defaultFunction = null;

public Rules() {
super();
}

public Rules(Collection<? extends Rules.Rule<F,T>> c) {
super(c);
}

public Rules<F,T> addRule(Predicate<? super F> condition, Function<? super F, ? extends T> action) {
add(new Rule<F,T>(condition, action));
return this;
}

public Function<? super F, ? extends T> getDefault() {
return defaultFunction;
}

public Rules<F,T> setDefault(Function<? super F, ? extends T> defaultFunction) {
this.defaultFunction = defaultFunction;
return this;
}

public Rules<F,T> noDefault() {
setDefault(null);
return this;
}

public T apply(F from) {
for (Rule<F,T> rule: this) {
if (rule.getPredicate().apply(from)) return rule.getFunction().apply(from);
}
Function<? super F, ? extends T> def = getDefault();
return def != null ? def.apply(from) : null;
}

public static final class Rule<A,B> {
private final Predicate<? super A> predicate;
private final Function<? super A, ? extends B> function;

public Rule(Predicate<? super A> predicate, Function<? super A, ? extends B> function) {
this.predicate = predicate;
this.function = function;
}

public Predicate<? super A> getPredicate() {
return predicate;
}

public Function<? super A, ? extends B> getFunction() {
return function;
}
}

}

 

2
Your rating: None Average: 2 (2 votes)
Published at DZone with permission of its author, Yardena Why.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Ricky Clarkson replied on Thu, 2008/05/01 - 9:53am

It's really awful that you choose such an ugly example to demonstrate Predicates and Functions.  I hope nobody gets put off functional programming by this.

Yardena Why replied on Thu, 2008/05/01 - 11:15am

Hi Ricky,

Hm :-( it's really discouraging to see you didn't like the example - I've been following your blog and projects and respect your opinion very much.

It is indeed something very simple that I took out of a unit test - I wanted to use an example that does not require any background and let people imagine how it can be used in other scenarios. Another "excuse" - the purpose of the article is to provide basis for another one that describes an improved version...

Anyway, I am really in the very early stages of learning functional programming - I'll keep learning :-) So thanks for the comment.

BTW would you consider this a better example?

A predicate to find out whether class Foo has an annotation @Bar like: @Bar class Foo {}

Predicate<Foo> isAnnotatedWithBar =  
self(Foo.class).reflect("annotations").cast(Annotation[].class).
compose(toIterable()).compose(
exists(self().reflect("annotationType").compose(isInstanceOf(Bar.class))));

 

Ricky Clarkson replied on Thu, 2008/05/01 - 12:18pm

Predicate<Foo> isAnnotatedWithBar = new Predicate<Foo>() {
    public boolean invoke(Foo foo) {
        return exists(foo.getClass().getAnnotations(), new Predicate<Annotation>(){ 
            public boolean invoke(Annotation ann) { 
                return ann.annotationType() instanceOf Bar; } } ); } };
 
I could probably make it a little bit prettier and easier to understand, but it's better for a few reasons:

 

1. It is orders of magnitude faster than using reflection for method calls.

2. It is compile-time typechecked.

3. Subjective, but I find code that uses lots of composition hard to read, except, so far, in Haskell, where instead of x.compose(y) you can write x . y, making chains easier to see. If x . y in a language is more than, say, twice as long as x (y arg) (Java: x(y(arg)) ), I think composition is probably not worth it. Couple that with the lack of typesafety in your code and I'd definitely say it wasn't worth it. In Haskell when I get confused I can compose a couple of functions together and then see the type clearly.

4. You had to introduce extra names such

As for a replacement for your original code, I'd rather the caller pass in a Lazy<Integer>, where Lazy looks like: interface Lazy<T> { T invoke(); }. Then that can return the right size, whatever happens, with the exception of null.

By the way, why do you write your code in the com.google.common.base package? Are you a googler, or do you just not like importing? ;)

Steven Baker replied on Fri, 2008/05/02 - 12:10am

holy heck thats some scary looking blackmagic coding!

no thanks to this, i like to actually be able to follow whats going on with my code, especially when debugging. 

Ricky Clarkson replied on Fri, 2008/05/02 - 8:44am in response to: Steven Baker

There's nothing in this (my version anyway!) that makes debugging hard.

Peter Dishchekenian replied on Fri, 2008/05/02 - 7:02pm

I'm compelled to make this my first post (hopefully, not last) here on DZone as a reaction to the following statement:

"Anyway, I am really in the very early stages of learning functional programming - I'll keep learning :-) So thanks for the comment."

Not to be insulting in any way, if you are in the early stages of learning, then why have you posted this instruction before gaining a firm grasp of functional programming?

Soylent Green replied on Sat, 2008/05/03 - 6:53am

This is fun. Compare the code-size, compare the readability of the "rule" example and the straight forward if-then-else...So what is the point you want to address here? Do yu want to show us an interesting way to make easy things complex? And by the way - though "rules" and "rule engines" are hyped it's not realy a rule-based approach you did here: I'd guess it's a GoF chain of responsibilty in a roundabout way…

 

Ronald Miura replied on Sun, 2008/05/04 - 10:01pm

Hahaha just please, don't create a framework for fp until you learn it :)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.