Stream of unconsciousness

Saturday, May 8, 2010

In combinatory logics there is an interesting thing called Y combinator which is used to implement recursion. Another name for it is fixed point combinator because it computes fixed point (e.g. f(x) = x) for functions.

Combinator Y applies function to its fixed point Y f = f (Y f) and it can be defined as Y = S S K (S (K (S S (S (S S K)))) K). The definition of the fixed point in Haskell is much simpler: fix f = f (fix f). The fix function evaluates to f ( f ( f ( .....)))) which seem to loop indefinitely. However, it does not either hang nor cause stack overflow because of lazy evaluation. The first argument fix f passed to f is evaluated only if it is needed for computation. For example, fix (\unused -> 0) gives result 0.

Function

factorial = fix (\f n -> if (n==0) then 1 else n * f (n-1))

computes a factorial of n. The function passed to fix takes a function f e.g. its fixed point and number n. fix passes only one argument to a function that takes two. The result is the function which takes the rest of the arguments. If n == 0 it returns 1, otherwise it calculates factorial for n - 1 and multiplies it by n. It is interesting that recursion is created using anonymous function which does not know its name and hence, cannot call itself. The factorial function can be rewritten with explicit recursion as

factorial = \n -> if (n==0) then 1 else n * factorial (n-1).

I wondered how fixed point combinator can be implemented in an object-oriented imperative language. I do mostly Java programming so I chose it for the experiment.

Making Java functional

Implementation of the fixed point combinator requires laziness. The methods(functions) are not first-class citizens and Java does not support partial application. So, before implementing fixed point combinator we need to overcome these problems.

To make the functions the first-class citizens we must represent them as objects. There are several methods to do it. The first is to create an interface with one method, which represents the function and implement it. The interface can be implemented either with a usual or anonymous class. In the latter case we can create the closures. Here is the example.

This implementation has two drawbacks. The first is that we need to write boilerplate code to accept parameters. The second is that the parameters must be objects, not primitives, although it is mitigated by autoboxing. They can be overcome by introducing a new interface for each signature, e.g. for function that takes int, String and returns String we create an interface like FunctionIntStringToString. It will give more type safety and allow accept parameters as usually, but if we use many functions with different types there will be even more boilerplate than before.

So I decided to switch to another variant. Java reflection mechanism has class Method, which, respectively, represents a method. I wrapped method with the class Partial which also handles partial application. This mechanism does not allow to create closures at place because we cannot get Method of anonymous class. But the methods are written in a usual style and we can wrap any static method in Partial, including even methods of other classes. There is less boilerplate code and it is separated from functions. The restriction to static is just a technical limitation of my implementation and it can be removed.

This class stores a method and a list of its parameters. We can apply arguments lazily using method apply. Method eval tries to execute the function with the parameters passed earlier.

The most interesting part in this class is method eval. Let's look at it closer. At first it computes arity - the number of arguments the function takes. Then if we have less parameters than arity, nothing can be computed and we just return this partial. If number of passed arguments is sufficient, we can invoke the function. Note there can be more arguments than function consumes. It is possible if function returns partial which will consume the rest of the arguments. In this case we cast the result to Partial, add the rest of the arguments and call eval recursively. The recursion can be replaced with a loop, but it will lead to more verbose code. The last clause is executed if arity and number of parameters passed are equal. In this case we just return the result.

Fixed point

Finally we have got a small functional framework and everything is ready to accomplish the initial task. Here we go!

Here one loop for is replaced with function map. It is possible to make this program look even more functional, for example, replace Java conditionals with function cond or generate the list with an analog of unfoldr.

Conclusion: Java is able to express some concepts of functional programming and combinatory logics.