Functors are defined as a function encapsulated in an object. In most cases, the functionâ€™s parameters can be set and the result retrieved using the common accessor pattern, such as setParameter1(Object value) or getResult(). The fact that these functions are encapsulated by real objects is also the reason for its greatest benefit: the use of many design patterns including structural ones such as the Decorator pattern and behavioral ones such as the Visitor pattern.
Functors in software design
While the classic object oriented design encourages inheritance and method overloading to achieve code reuse and code specialization, the functorsâ€™ design promote another approach, of composition. Functors are created to accomplish simple tasks and are later connected together, by connecting one functorâ€™s result to anotherâ€™s parameter, to satisfy a greater goal of the application.
By combining different functors together, one can in fact create a proxy functor, which takes parameters for its encapsulated functors, and returns a result which is in fact the result of the last functor in its composition. As an example, take the classic genetic algorithm cycle of population initialization, evaluation, selection, breeding, evaluation again, replacement of older generation and back to selection. Each of these can be defined by a functor, and the output of each is connected as the input of the next. In fact, the main iteration (selection, breeding and evaluation) can be defined by a functor as well, and then called iteratively until the stopping condition is reached.
The best Java functorsâ€™ framework I could find out there is Java Generic Algorithms, or JGA. It offers the generic functor, and on top of that offers the three most common functors, being Generator (no parameters), UnaryFunctor (single parameter) and BinaryFunctor (two parameters). Functor composition is available through the ability to bind any parameter of a functor to any other functor or constant value. To continue our previous example, letâ€™s composite the main GA iteration using JGA:
And in code:
UnaryFunctor<Population, Individual> selector = â€¦; BinaryFunctor<Individual, Individual, Individual> breeder = â€¦.; UnaryFunctor<Individual, Individual> evaluator = â€¦; UnaryFunctor<Population, Individual> composedBreeder = breeder.compose(selector, selector); UnaryFunctor<Population, Individual> composedEvaluator = evaluator.compose(breeder); return composedEvaluator;
Later on, this generated functor can be used and composed with other functors, such as a functor to iteratively produce new offsprings for the new generation and a functor to check for the end-condition. Speaking of the end-condition functor, these special functors with a result of a boolean are sometimes referenced to as Predicates, and are more widely known and used.
Real life usages of functors
The best use for functors in an application that I have seen yet is in fact not in the Java realm, but in Appleâ€™s Core Image framework: by creating image manipulation functions (the Image Units) and exposing their functionality as functors, this framework allows anyone to create endless image manipulations, simple or complex, with very little amount of code. In fact, Apple went the extra mile by providing an application for composing these functors together without a single line of code, called Quartz Composer (if you own a Mac, you really want to take a look at it – itâ€™s free!)
There are two differences between the way Core Image functors and JGA functors work, though: first, it allows for multiple results in addition to multiple parameters, referenced to as input and output ports. Second, it uses named ports instead of numbered ports, which makes it somewhat easier to follow and document. As an experiment, Iâ€™ve tried implementing a functor framework more similar to the CI approach. It works, and I might publish it here soon.
Thereâ€™s a lot to say about functors, their different usages and some common problems one might encounter when working with them. Iâ€™ll try to cover more about this way of development later, as it allows the creation of very dynamic applications and very sophisticated frameworks.