How much would you pay for a closure?
June 13, 2009
I know there's been discussion about this in the Java world, but I
don't see the cost/value benefit. Perhaps you can enlighten me!
Today we build closures in Java the hard way. We build objects that
encapsulate the computation and give 'em an execute() method that does
the work. It's not hard to do and we have a whole variety of places
where we use this. Most of the time we don't even think in terms of a
"closure" per-se, just an object that will produce the desire result
when called on.
On rare occasions we use inner classes so that we can close over some
local variables. It's a bit awkward, but it is rare.
Most of our code we write in the "old fashioned"
way--call/return. 'Cause it works really well and it's easy to
understand and it mirrors the real world.
Everything you add to a language costs something. It makes the
language more complex and harder to use. It often has non-obvious
computational expenses. The question is "Does the feature make writing
programs easier?" And if so, how much?
For example, "auto boxing", which seems so clever at first glance,
harbors some ugly little secrets.
Auto boxing is where the compiler "fixes up" your code by adding a
method call that you didn't write. The issue is that primitives are
not of type Object, like everything else. If this was a wise thing to
do is an open question. But it was done. So normally, this would be
illegal:
void doStuff(List list, int i) {
list.add(i);
...
because add is expecting an Object. If you really wanted to add an int
to this list, you'd have to do the boxing yourself:
void doStuff(List list, int i) {
list.add(new Integer(i));
...
By allowing the compiler to do this for you, it's easy to miss the
fact that this is going to be a very expensive call. And if what you
really wanted was a list of ints, you'd be unhappy.
So is auto boxing a good thing because it saves some typing. It's bad
because it can fool you. It's also a very rare thing to ever
do. Normal programmers will never see a situation where they want to
pass an int as type Object.
(Write a ListInt class!)
Only us reflective guys do this much. And we avoid auto boxing,
because we always want more control over what our program is doing in
these situations.
In Lisp languages (I'm gonna futz a bit with Scheme style, just
details), a function is (mostly) a first class object. Meaning that
you get closures practically for free. You could write this:
(setq plus1 (lambda(a) (+ a 1)))
(plus1 2) => 3
and more generally you can "close over" variable bindings:
(setq plus3 ((lambda(inc) (lambda(a) (+ a inc))) 3))
(plus3 4) => 7
It works great and the compilers can produce excellent binary from
this.
By contrast, to do the same in Java, you must construct an object and
call a named method:
plus4 = new Plus(4);
plus4.add(5) => 9
class Plus {
int inc;
Plus(int inc) {
this.inc = inc;
}
int add(int i) {
return i + inc;
}
}
Clearly the Java code is much more verbose than the Lisp code and it compiles
better then the Lisp code only because it has type declarations.
One could imagine a syntax in Java:
int inc = 6;
Closure plus6 = Closure(int i) {return i + inc;};
plus6(7) => 31
This would save the poor programmer from 10 additional lines of
code. Clearly, that's good. But is it worth it? Now the language is
more complex and there are lots of complications in the implementation
of efficient closures. And details for the programmer to understand.
I don't like learning weird little details.
The question for me is "Will I use this feature enough and is it
obvious enough in its usage to be worth including in the language?"
Macros, for example, are one of the banes of C/C++. They are so clever
and do such wonderful things, and have a bad habit of not being quite
as straight-forward as one would like. At the last count,
1,234,656,993 hours have been wasted due to subtle bugs in macros.
When I was writing a lot of Lisp, back in the 70s and 80s, I used
closures once in a blue moon. And I liked using them. I actually read
Church's thesis!
Without closures, I would have to rewrite 0.0194% of my code.
Is it worth the complication to save 0.0194% of my code?
No.
IMHO.
Generics in Java: good!
Autoboxing: bad
Delegates in C#: bad
Closures in Java: bad
Macros anywhere: bad
Waddya think?
-Bil