I was recently asked by my supervisor to have a look at the lambda abstractions library that is added to the new Java JDK (Java 8). Then, we had the idea of implementing Church numerals using two different programming techniques. One is to implement it using the visitor design pattern, and the other is to use the new lambda abstractions in java 8.
Church numerals is a way of presenting natural numbers using lambda calculus. It uses the famous algebraic idea of presenting zero and then presenting the other natural numbers using the successor function. Church presented natural numbers using the following lambda expressions :
The idea here is that you get two input functions (s and z). If you applied only z, then you are presenting zero. If you applied s on z (once) you get the presentation of the number one. if you applied s twice on zero ( λs. λz. s (s z) ) then its the presentation of the number 2, and so on..
To present Church numerals in Java using the visitor design pattern. I started with implementing the Visitor interface, It will have two methods, one is to replicates the zero function and the other as the successor function.
I presented the natural numbers with three classes, an abstract class (NatNum) for all natural numbers, class Zero and the class Successor.
Church numerals is a way of presenting natural numbers using lambda calculus. It uses the famous algebraic idea of presenting zero and then presenting the other natural numbers using the successor function. Church presented natural numbers using the following lambda expressions :
- λs. λz. z ------> representing zero.
- λs. λz. s z -------> representing successor of zero.
The idea here is that you get two input functions (s and z). If you applied only z, then you are presenting zero. If you applied s on z (once) you get the presentation of the number one. if you applied s twice on zero ( λs. λz. s (s z) ) then its the presentation of the number 2, and so on..
To present Church numerals in Java using the visitor design pattern. I started with implementing the Visitor interface, It will have two methods, one is to replicates the zero function and the other as the successor function.
1 2 3 4 5 6 7 | public interface Visitor { public <T> T visitZero(Zero z); public <T> T visitSucc(Successor s); } |
I presented the natural numbers with three classes, an abstract class (NatNum) for all natural numbers, class Zero and the class Successor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | abstract class NatNum { public abstract <T> T visit(Visitor vis); } class Zero extends NatNum { @Override public <T> T visit(Visitor vis) { return (T) vis.visitZero(this); } } class Successor extends NatNum { NatNum of; public Successor(NatNum off) { of = off; } @Override public <T> T visit(Visitor vis) { return (T) vis.visitSucc(this); } } |
The classes have the to implement the method visit(visitor vis) . The idea here is that visitZero is applied when the number zero visits the Visitor interface. The same applies for the Successor, but in that case visitSucc is used. I did that to differentiate between the zero function and the successor function in the Church numerals. Now, by writing a class that implements the interface Visitor, we can define the behavior of the visitZero and visitSucc functions. I will show this in an example later.
To present Church numerals using the new lambda abstraction in Java 8 - I actually advice you to read the manual of lambda abstraction on Oracle website -, I made also three classes, however, instead of taking a Visitor class in the argument of the method, I take two functions ( fZero and fSucc)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | abstract class NatNum { public abstract <T> T visit(Function<Zero, T> fZero, Function<Successor, T> fSucc); } class Zero extends NatNum { public <T> T visit(Function<Zero, T> fZero,Function<Successor, T> fSucc){ return fZero.apply(this); } } class Successor extends NatNum { NatNum of; public Successor(NatNum off) { of = off; } public <T> T visit(Function<Zero, T> fZero,Function<Successor, T> fSucc) { return fSucc.apply(this); } } |
The function fZero takes a Zero object and returns an object of generic type T. fSucc takes a Successor object and returns also an object of type T. In the visit function of the Zero object, fZero is applied on the instance of the object. In contrary with the Successor object as fSucc is applied on the Successor object.
The result here is almost the same when we apply an operation on the Church numerals, either with the visitor pattern or with the lambda abstraction. I worked on a small example that evaluates the given Church numeral and returns its value in integers. For example, if I have ( λs. λz. z), it should return the number 0. λs. λz. s (s (s z)) should return 3.
For the visitor pattern, I implemented a class "EvalVisitor" that implements the Visitor interface,
1 2 3 4 5 6 7 8 9 10 | public class EvalVisitor implements Visitor { Integer visitZero(Zero z) { return 0; } public Integer visitSucc(Successor s) { return Integer.valueOf(new Integer(1) + (Integer) (s.of).visit(this)); } } |
The basic idea of EvalVisitor is that when the method visit is applied on a Zero object and is redirected to the method visitZero, it will just return the result zero. If it is a Successor object, then visitSucc will be applied on the object by adding 1 to the recursive call (visit ) on the rest of the Church numeral.
For the lambda calculus, we can think of the problem in a completely functional way, for example we have the function eval that takes a Church numeral and returns an integer, it is defined as follows:
eval(Zero) = 0.
eval(Sucessor(x)) = 1 + eval(x).
In java, we create the method "eval(NatNum x)" which is defined as follows:
1 2 3 | public static Integer eval(NatNum num1) { return (Integer) num1.visit(n -> 0, n -> 1 + eval(n.of)); } |
It uses the method visit on the given NatNum, providing two functions, one is a simple function returning 0, which is applied on the Zero objects and one that returns 1 and recursively call eval on the of of the Successor object.
My point of view is that the potential of lambda abstractions and functions in Java 8 will reduce the amount of code written making it easier to read and less buggy ! I will add more examples in another post in the following days !
PS, its my first technical post ever so I apologize if the writing technique is not that good !
No comments:
Post a Comment