Java As A Functional Programming Language
Java As A Functional Programming Language
Anton Setzer?
1 Introduction
This article was inspired by the web article [Alb]. In it the language C# is
compared with Java. It reveals some of the background why Microsoft decided
to introduce a new language C# instead of further developing their variant of
Java. When reading this article one gets the impression that, apart from a gen-
eral conflict between Sun and Microsoft, the main dispute was about delegates.
Hejlsberg, at that time chief architect of J++ and later the architect of C#,
wanted to add delegates to J++. Gosling, the designer of Java, refused that,
because in his opinion all non-primitive types should be reduced to classes.
Delegates, as proposed by Hejlsberg, are classes that act as function types. An
object of a delegate can be applied to arguments of types specified by the delegate
and an element of the result type is returned. Delegates can be instantiated by
passing a method with an appropriate signature to them – then applying the
delegate is the same as applying that method to the arguments (this might
?
Supported by the Nuffield Foundation, grant No. NAL/00303/G
2 Anton Setzer
By a Java type – we will briefly say type for this – we mean any expression
htypeexpri, which can be used in declaring variables of the form htypeexpri f or
htypeexpri f = · · ·. So the primitive types boolean, char, byte, short, int, long, float,
double and the reference types arrays, classes and interfaces are types. Note that
void is not a type.
A class can be seen as a bundle of functions, which have state. Therefore,
the type of functions is nothing but a class with only one method, which we call
ap. Applying the function means to execute the method ap. Therefore, if A and
B are Java types, we define the type of functions from A to B, A → B, as the
Java as a Functional Programming Language 3
following interface (we use the valid Java identifier A B instead of A → B):
When defining higher type functions, we need to pass parameters to nested inner
classes. An inner class has access to instance variables and methods of classes, in
the scope of which it is, but only to final local variables and parameters of meth-
ods. So, in order to make use of bound variables in λ-terms, we need to declare
them final. Parameters can be declared final when introducing them. As an ex-
ample, we introduce the λ-term λf.λx.f (x + 1) of type (int → int) → (int → int).
Depending on the parameter f we introduce λx.f (x + 1), which is introduced by
an inner class. The code reads as follows:
λ-abstract their arguments. The ζ-calculus will serve however only as a heuristic
for the following definitions.
We consider finitely many Java types σi0 and extend these types by types
formed using →, where we identify higher types built from σi0 with their trans-
lation into Java types using interfaces. We assume that the basic types σi0 are
not the Java translation of a type constructed from other σj0 using →.
We assume that our idealized Java compiler evaluates each Java expression
t of the restricted Java language in the environment ρ to an element [[ t ]] jρ of a
set Aρ of natural numbers, provided the evaluation terminates (the superscript
j stands for “Java”). For the interface ρ with distinct methods fi (i = 1, . . . , n)
having arguments of type ρi,1 , . . . , ρi,ki and result type σi , Aρ is supposed to be
the set of sequences hhdf1 e, g1 i, . . . , hdfk e, gk ii (coded as natural numbers) where
gi is an element of (Aρi,1 × · · · × Aρi,ki ) → Aσi . This means that the interface
type is interpreted as a record of functions. B → C is the set of partial recursive
functions from B to C and B × C the set of pairs hb, ci for b ∈ B and c ∈ C, and
both sets are coded as sets of natural numbers in the usual way. As a notation
we will use λλx.t for a code for the partial recursive function f s.t. ∀x.f (x) ' t.
Gödel brackets will be omitted in the following.
If [[ t ]] jρ = hhf1 , g1 i, . . . , hfk , gk ii, we assume that [[ t.fi (s1 , . . . , sl ) ]] jρ '
gi ([[ s1 ]] jρ , . . . , [[ sl ]] jρ ). Here, application is strict, so if at least one [[ si ]] jρ is un-
defined, the result is undefined. (This is call by value evaluation, as usual in
imperative programming languages. In fact the real Java compiler evaluates an
expression t.fi (s1 , . . . , sn ) by first evaluating s1 , . . . , s1 in sequence and then pass-
ing their results as parameters to the body of t.f, which is then evaluated. Because
we have excluded imperative concepts, this is equivalent to strict application.)
[[ t.fi (s1 , . . . , sl ) ]] jρ is undefined, if [[ t ]] jρ is undefined.
We assume the interpretation [[ tk ]] jρ of certain Java expressions tk of base
Java type σk has been given. tk might depend on free variables, provided their
type can be defined from base types using →. This allows to treat terms of
higher Java types t essentially as base terms, by applying them to variables such
that the result is not the translation of an arrow type – for instance, instead of
λx, y.x + y we can take x + y as base term.
We take as model of the typed lambda calculus, based on base types σi0 and
base terms tσk k , the standard model of partial recursive higher type functions
(see e.g. 2.4.8 in [Tro73]), based on Aσi0 . The interpretation of ρ will be called
Bρ . So Bσi0 := Aσi0 and Bσ→γ := Bσ → Bγ .
The translation trans of λ-terms into Java is defined as follows:
trans(tk ) := tk
Otherwise: trans(x) := x
trans(r s) := trans(r).ap(trans(s))
trans((λx.r)σ→τ ) := (λj x.trans(r))σ→τ , where
(λj x.s)σ→τ := new (σ → τ )(){τ ap(final σ x){return s; }; };
Next we assume that Java evaluates variables and λj -terms as follows:
[[ x ]] jρ ' ρ(x) [[ λj x.s ]] jρ ' hhap, λλy.[[ s ]] jρ[x/y] ii
6 Anton Setzer
b↑ := b, if b ∈ Bσi a↓ := a, if a ∈ Aσi
b := hhap, λλx.(b(x )) ii, if b ∈ Bσ→ρ a↓ := λλx.(f (x↑ ))↓ , if a = hhap, f ii ∈ Aσ→ρ
↑ ↓ ↑
[[ tk ]] ρ :' [[ tk ]] j↑◦ρ
Otherwise [[ x ]] ρ :' ρ(x) [[ λx.s ]] ρ :' λλy.[[ s ]] ρ[x/y]
[[ r s ]] ρ :' [[ r ]] ρ ([[ s ]] ρ )
4 Applications
Untyped Lambda Calculus and solutions of domain equations. We can extend our
notion (A1 , . . . , An ) → A so that Ai might include the word self, standing for the
type one is defining (i.e. the type (A1 , . . . , An ) → A). So (A1 , . . . , An ) → A is the
interface defined by
Untyped lambda terms can be encoded in a direct way into (self → self). The
following defines λx.x, λx.xx and Ω (evaluating the last line will not terminate):
defines the set of polymorphic functions, mapping int to int, String to String.
Elements of poly have to be introduced using (anonymous) inner classes, but
might extend functions (int → int) or (String → String) introduced by λ.
Explicit overriding. In object-oriented programming overriding means that
one defines a class, which extends another class, but redefines some of the meth-
ods of the original class. If a method of the original class is not overridden, and
calls a method, which is overridden, then in the new class this method call will
now refer to the new method. The difficulty with overriding is that it is not clear
which methods are affected by overriding and which not. Functions allow us to
control this in a better way.
We take an example. Assume a drawing tool Tool, which has one method
void drawLine (Point x,Point y) drawing lines from point x to y, and one method
void drawRectangle (Point x,Point y) drawing a horizontally aligned rectangle with
corners x and y, which makes use of drawLine. (The class Point will be essentially
a record consisting of two floating-point numbers for the x- and y-coordinate).
The change to a new method drawLineprime is usually done by overriding this
method.
If we want to separate concerns, we have the guarantee that drawRectangle
only depends on drawLine. This can be achieved by implementing the above in
the following way:
This provides f with a context, which it can modify. That context can encode
local variables, which are otherwise not accessible by f, so that they can be read
and modified by it. Note that this parameter can be the object calling f.
5 Algebraic Types
In functional programming, apart from the function type the main construction
for introducing new types are algebraic types. Algebraic types are introduced
by choosing a new name for it, say Newtype, and some constructors C1 , . . . , Ck ,
which might take as arguments arbitrary types and have as result an element
of Newtype, which is the element constructed by the constructor. So Ci are of
type (Ai1 , . . . , Aimi ) → Newtype. Aij can be arbitrary types, which might refer to
Newtype. The algebraic type Newtype is the type constructed from Ci . More pre-
cisely, in the model the algebraic version of Newtype is the least set such that
we have Ci of the aforementioned types and such that for different choices of
i, xi1 , . . . , ximi , Ci (xi1 , . . . , ximi ) are different. The coalebraic version is the largest
set, s.t. every element is of the form Ci (xi1 , . . . , ximi ), where xij is of type Aij , and
all such elements are different. Although, because of full recursion, in functional
programming one always obtains the coalgebraic types (one obtains infinite el-
ements like S(S(S(· · ·))) in case of the co-natural numbers), one usually talks
about algebraic types. The standard notation for the algebraic type introduced
is
type Newtype = data {C1 (A11 x11 , . . . , A1m1 x1m1 ) | · · · | Ck (Ak1 xk1 , . . . , Akmk xkmk )}
f, defined by the element c of type Cases, should compute to c.caseCi (xi1 , . . . , ximi ).
We call this principle of forming functions f as usual in type theory elimination
(since it inverts the construction of elements of the algebraic type by construc-
tors), and use identifier elim. In a first implementation in Java, we define elim as
a method of Newtype, which determines, depending on c, the result of that case
distinction used for the current element. So elim is a method Object elim(Cases c).
However, we will see, that the constructor and its arguments are coded into
elim and we want to define later selfupdate, which changes the constructor and
its arguments introducing an element. For this we need method updating, and
therefore replace the method elim by a variable elim of type Elim, where
Note that Newtype is introduced by its elimination rules. This suffices, since from
the elimination rules for a constructed element we can retrieve the constructor
introducing it (using caseCi (~x) = Integer(i)) and the arguments of the constructor
(for retrieving xij , if it is an object, let caseCi (~x) := xij , caseCk (~x) := null (k 6= i)).
Now we define the constructors. Ci should return, depending on its argu-
ments ~x, an object of Newtype, which amounts to introducing a suitable element
12 Anton Setzer
elim. Above we have said that in case of an element introduced by Ci , elim ap-
plies c.caseCi to the arguments of the constructor. Therefore, elim for Ci (~x) is
λ(Cases c) → {return c.caseCi (~x)}. The definition of Ci is therefore:
– Newtype.Ci is a function with arguments Ai1 xi1 , . . . , Aimi ximi and result of
type Newtype, the type of the constructors.
– The type of elim is that of the elimination rule for the algebraic type.
– For s := Newtype.Ci (ai1 , . . . , aimi ) it follows s.elim.ap(c) reduces to
c.caseCi (ai1 , . . . , aimi ).
– Therefore, we have implemented the constructors and elimination constants
of the algebraic data type s.t. the desired equality between the two holds.
(Note that since we always have full recursion, there is no need to include
the recursion hypothesis as parameter into the type of elim).
In Java we introduce them separately, and the type checker takes care of the
mutual dependencies. When using elim however, we will usually have to define
simultaneously functions from Even and from Odd into desired result types.
More efficient implementations. It’s easy to add more efficient implementa-
tions. For instance, assume we define the natural numbers Nat as
data{zero | succ(Nat n)}. This implementation will have problems in representing
reasonably large numbers. We can add however a new constructor:
which converts integers into natural numbers. This addition can still be done
by referring to the abbreviation data{zero | succ(Nat n)}, only the new method
has to be added. After this definition, because of elim, the new version of Nat
can still be seen as an implementation of the co-natural numbers, which is the
co-algebraic version of the natural numbers.
Java as a Functional Programming Language 15
However, we still have a problem: conversion back into integer, defined via
elim, will be inefficient. The solution is to add a new instance variable nat2int
of type () → nat, which is calculated directly by all constructors and by selfup-
date. We obtain a fast conversion from nat to int, and can define operations like
addition by referring to that implementation.
Infinite elements. Using the constructors, we cannot define an infinite element
of an algebraic type, like the natural number n = succ(n). This is because by
call-by-value, this recursive definition will result in non-termination. However,
we can define such numbers by using selfupdate:
6 Extensions of Java
7 Related Work
7.1 Function types and λ-Terms.
Related work in Java. Martin Odersky and Philip Wadler ([OW97]) have devel-
oped Pizza, an extension of Java with function types, algebraic types and generic
types, with a translation into Java. Their encoding of λ-terms is longer, since
they do not use inner classes, but encode inner classes directly using ordinary
classes. The encoding of algebraic types is more direct, but does not hide the im-
plementation. The generic part of Pizza (without the functional extensions) has
been developed further into an extension of Java called GJ ([BOSW98], which
was discussed in Sect. 6.
Related work in C++. The main problem of C++ is that one does not have
inner classes – nested classes do not have access to variables of enclosing classes.
This makes the introduction of nested λ-terms much more involved. There are
several approaches to introducing higher type functions into C++. One is [Kis98],
in which pre-processor macros are used in order to generate classes correspond-
ing to λ-expressions. His approach does not allow nested λ-expressions. Järvi
and Powell [JP] have introduced a more advanced library in C++, for dealing
with λ-terms, but have as well problems with nested λ-terms (see the discussion
in 5.11 of the manual). Striegnitz and Smith [SS00] are using expression tem-
plates ([Vel95], [Vel99], Chapter 17 of [VJ03]) in order to represent (even nested)
λ-terms. By using that technique, the body of a λ-term is converted into a parse
tree of that expression. The parse tree contains an overloaded application oper-
ation, and when applied to arguments, substitution of the bound variables by
the arguments and normalization is carried out. So, normalization is to a certain
extend done by hand, whereas in our approach one uses the already existing
reduction mechanism of Java (this is in some sense normalization by evaluation,
cf. [BS91]). The body of the λ-terms is not allowed to have imperative constructs
Java as a Functional Programming Language 17
and all C++ functions used must first be converted into elements which provide
the mechanism for forming parse trees (generic functions are provided for this).
Related work in Perl and Python. Perl is an untyped language and therefore
has no function types. It has first class function objects, which can be nested
and have nested scopes. Therefore the function body of a nested function has –
differently from C++ – access to variables of all functions, in the scope of which
it is. Function objects do not have an explicit argument list. Instead the body
has access to the list of arguments of this function. Therefore it is possible to
define anonymous functions, which is the same as having λ-terms, and therefore
the untyped λ-calculus is a subset of Perl. The details can be found in Mark-
Jason Dominus’ article [Dom99]. Python has λ-terms as part of the syntax and
since version 2.1.it has the same scoping rules as Perl, therefore it contains as
well the untyped λ-calculus.
Comparison with the approach in [OW97]. Odersky and Wadler have used a
different technique for implementing algebraic types. Essentially, an implemen-
tation of Tree in their setting has an integer variable constructor, which deter-
mines the constructor, and variables (nat n, Tree l,Tree r), which are defined as
(n1 , l1 , r1 ) in case the element is constructed as branch(n1 , l1 , r1 ), and undefined,
if it is a leaf. In order to carry out case distinction however, the variable con-
structor has to be public, and can then for instance be set to values that do not
correspond to a constructor. This is not a problem in their setting, since they
consider an extension of Java – so constructor is only visible in the translation of
the code back into Java. Our goal however is that the original Java code repre-
sents the algebraic type and hides implementation details which should not be
visible to the user. We have achieved this because of elim: this variable expresses
that the types introduced are coinductive – every element must be considered as
a constructor applied to elements of appropriate types. (Unfortunately, since we
have only the type Object available, one could still introduce silly elements like
returning an object which simply returns one of the cases without applying it to
arguments – if one uses in an extension of Java by templates a generic version
of elim as in Sect. 6, this will not be possible).
Comparison with the visitor pattern. From Robert Stärk we have learned that
our encoding is closely related to the visitor pattern ([GHJV95], [PJ98]; see as
well [ZO01] and [KFF98] for applications to extensible data types).
If one applies the standard visitor pattern to the Tree example above, one
has an interface Tree. Its definition is as follows:
interface Tree{ public void accept(Visitor v);}
Tree will have two subtypes, Leaf and Branch. The interface Visitor is then defined
as follows:
interface Visitor {
public void visit(Leaf leaf);
18 Anton Setzer
Conclusion
We have seen that there is a direct embedding of function types in Java, which
makes use of inner Classes. This can be done easily by hand, but having some
syntactic sugar (like (A1 , . . . , An ) → B or λ(A1 x1 , . . . , An xn ) → {· · ·}) added to
Java would be of advantage. However we believe that this should be just syntactic
sugar – then we are able to extend function types to richer classes and introduce
Java as a Functional Programming Language 19
functions with side effects. We have given a direct encoding of algebraic data
types into Java. With generic types this encoding would be smoother and more
in accordance with standard type theory.
References
[AC94] M. Abadi and L. Cardelli. A semantics of object types. In Proceedings of the
9th Symposium on Logic in Computer Science, pages 332–341, 1994.
[AC96a] M. Abadi and L. Cardelli. A theory of primitive objects: Untyped and first
order system. Information and Computation, 125(2):78–102, 1996.
[AC96b] M. Abadi and Luca Cardelli, editors. A Theory of Objects. Springer, Berlin,
Heidelberg, New York, 1996.
[Alb] Ben Albahari. A comparative overview of C#.
http://genamics.com/developer/csharp comparative.htm.
[B+ 01] Gilad Bracha et al. Adding generics to the Java programming language:
Participant draft specification.
http://jcp.org/aboutJava/communityprocess/review/jsr014/index.html,
2001.
[Bir98] Richard Bird. Introduction to functional programming using Haskell. Pearson
Education, Harlow, second edition, 1998.
[BOSW98] Gilad Bracha, Martin Odersky, David Stoutamire, and Philip Wadler. Mak-
ing the future safe for the past: Adding genericity to the Java programming
language. ACM SIGPLAN Notices, 33(10):183–200, 1998.
[BS91] U. Berger and H. Schwichtenberg. An inverse of the evaluation functional for
typed lambda-calculus. In R. Vemuri, editor, Proceedings of the Sixth Annual
IEEE Symposium on Logic in Computer Science (LICS), pages 203 – 211.
IEEE Computer Society Press, 1991.
[Dom99] Mark-Jason Dominus. Pure untyped lambda-calculus and popular program-
ming languages. J. Funct. Progr., pages 1 – 7, 1999.
[GHJV95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design
Patterns. Elements of reusable object-oriented software. Addison-Wesley, 1995.
[GJSB00] J. Gosling, B. Joy, G. Steele, and G. Brache. The Java Language Specifica-
tion. Addison-Wesley, second edition, 2000.
[IP00] Atsushi Igarashi and Benjamin C. Pierce. On inner classes. In European Con-
ference on Object-Oriented Programming (ECOOP), volume 1850 of Springer
Lecture Notes in Computer Science, pages 129–153, 2000.
[Jav02] The Java Language Team. About Microsoft’s delegates.
http://java.sun.com/docs/white/delegates.html, 2002.
[JP] Jaakko Järvi and Gary Powell. The lambda library. Available from
http://lambda.cs.utu.fi and http://www.boost.org/libs/lambda/doc/.
[KFF98] S. Krishnamurthi, M. Felleisen, and D. Friedman. Synthesizing object-
oriented and functional design to promote re-use. In European Conference
on Object-Oriented Programming, pages 91 – 113, 1998.
[Kis98] Ole Kiselyov. Functional style in c++: Closures, late binding, and
lambda abstractions. A poster presentation at the 1998 Interna-
tional Conference on Functional Programming (ICFP’98), available from
http://okmij.org/ftp/c++-digest/Functional-Cpp.html, 1998.
[Lan66] P. J. Landin. The next 700 programming languages. Communications of the
ACM, 9(3):157–164, March 1966. Originally presented at the Proceedings of
20 Anton Setzer