Closures without Function Types
By john.rose on Aug 25, 2006
There is a proposal afloat in several places including Javalobby by some of Java's authors to add closures and function types to Java. The authors are Gilad Bracha, Neal Gafter, James Gosling, and Peter von der Ahé, and so I will tag their proposal as BGGA.
For my part, I think Java needs (a) better closures and (b) slightly better type parameters but not (c) function types. Let‘s examine the last first…
(c) Not Functions
There are various interesting lesser details to worry over, but the major impact of the BGGA proposal is to introduce a new subsystem (type scheme) of function types. It is a thoughtfully designed system, but it overlaps heavily with a pre-existing and parallel subsystem of interface types. This overlap with pre-existing functionality robs function types of the power they would have in another language. (I say this with great fondness for the functional programming style, as a long time Lisp programmer and author of an optimizing Scheme compiler.)
In a nutshell, any use case for a function signature
U(V,W...) throws X can be addressed by a one-method interface
I with a method
U I.m(V,W...) throws X.
(There‘s a little more than a nutshell‘s worth; keep
reading.) Thus, whatever value the new type system has must somehow
exceed the utility of one-method interfaces.
The introduction of generic types was also a major impact on the
language, but type parameters compensated for their complexity by
addressing a serious flaw in interface types, a widely felt flaw:
List was less workable than
There is not a corresponding widely felt need which function types
address, because using one-method interfaces for function types has
been a reasonable workaround.
Here are some benefits (real and perceived) of function types, examined critically in the setting of Java:
Names are Distractions
It‘s easier to manage nameless types, because names can be misleading and sometimes cause useless type mismatches. But
classes offer various workarounds for name management problems. For
example, an API which features a method that takes a functional
parameter can also feature a nested interface to name the parameter's
type: The parameter type need not be anonymous. The most common handful
of types (such as
Runnable) can be in
java.lang, while other function-like types can be API-specific.
The occasional remaining mismatch can be patched by closure creation or
an adapter generation convention without recourse to function types.
Pure Structure is Concise
Similarly, function types can be concise, since they omit extraneous names. But
the names are not always extraneous. They often convey useful
intentions to the programmer, especially if accompanied by a Java
documentation comment. Even if the function type is pure structure, a
symbolic name can abbreviate it; the name is rarely much longer than
the spelling of the type itself. The name is much longer only with the
simplest types, like
Identical calling sequences are fully compatible, instead of
being artificially distinguished merely by name. This leads to power
when combining separately designed modules, when they serendipitously
apply to the same function types. But this requires a
system designed for serendipity. Function types that are logically
similar must tend to be identical, or else the system will have to
adapt to the near misses of calling sequence. Java has too many types.
Consider the difference in Java between
throws IOException versus no throws, and varargs versus “vanilla”. Functional type systems tend to avoid such distinctions. Java API designers have too many degrees of freedom to expect to get lucky matching independently authored calling sequences.
Mismatches Easily Patched
Logically similar calling sequences can inter-convert (in BGGA). This can make up near-misses between types of values exchanged by unrelated APIs. But this is a two-edged sword, because (except at the point of closure creation) the VM probably has to build an adapter wrapper of some sort, which becomes an invisible overhead. (Or else function types are partially erased in the VM, which forces dynamic checking overheads, compared to strongly typed interfaces.) Better to have calling sequence conversion happen (a) at closure creation and (b) as an explicit construct (e.g., cast, new closure, or delegate creation syntax)
...Like Peas and Carrots
Languages with closures (Haskell, etc.) generally integrate them
with function types; it is a well-understood paradigm. Definitions of
the term closure tend to specify that a closure is a function. But
two of the oldest languages with closures, Scheme and Smalltalk, do not
have a system of function types per se. (Scheme has just one type,
which covers both closures and intrinsics. Smalltalk calls its closures
“blocks“, and manipulates them as — what else?
— one method objects.) Truly closure-like referential
transparency without function types can be observed in more recent
languages also, notably Beta and Java itself, whose inner classes act
as limited closures. In its essence, a closure is a source-language
construct which provides a runtime handle to a snippet of code. The
“closing“ of a closure refers to the fact that the code
snippet has full and seamless access to the source code around the
closure. The code inside the snippet is “in synch” with the
code outside; the same names mean the same things. This concept is
about factoring source code, not about type systems. There is surely a
type system to help classify the snippet handles, but interfaces can do
that job as well as pure function types.
(Your Point Here)
(Function types surely provide other advantages which I‘ve overlooked. I‘m also sure those advantages are diluted in the setting of Java.)
These various advantages, properly weighed, do not add up to a mandate for a new type subsystem, as long as the crucial advantage (better closures) can be obtained without the new types. There is much to like about BGGA including concise closure creation, better closure semantics, and local functions. But the extra types don‘t really carry their weight.