Thoughts on CICE (closures?)
CICE is a proposal by Doug Lea, Josh Bloch and ‘Crazy’ Bob Lee. It is being touted as a “closures” proposal for Java 7 alongside Neal Gafter’s BGGA (for instance). Here are some thoughts I had after reading their document: So basically, CICE is syntactic sugar for anonymous classes. In Guice parlance, the following: …becomes: Scheme is one of my favorite languages. Scheme basically invented closures, so given this–what are my reactions to CICE? First, this is what I call a closure: A block of statements which retains its lexical context indefinitely. A closure is also a first-class citizen, in that it can be stored in a variable and passed around like an object . In CICE, you don’t implictly close over the wrapping lexical context Uh, sure but what does that mean? It means local variables are not part of the closure’s immediate scope. Even though the appear to be: … won’t work. r only sees x = 1. Local variables are actually copied to the block. By hiding the final keyword it fools you into believing this is not the case. The fix for this is to declare x as public: I thought closures are supposed to prevent such malarkey, not induce it =P Note: The BGGA proposal also added something like this not long ago, but it was an optionaldocumenting annotation @Shared, rather than a language construct (i.e. public keyword). Statement blocks are not anonymous Blocks in almost every language thus far are anonymous (Ruby, Groovy, Python, etc.). A block in CICE is just a contraction of an instance creation step, so you still need to specify its type. The following (anonymous method) is not possible: Statement blocks are too type-restrictive By the same token, statement blocks are restricted to pre-existing types (i.e. single-method interfaces being subclassed). One powerful feature of closures in Scheme (and other dynamic languages) is they are type-neutral at the point of definition. This may seem cribby to you but: …can add two integers, longs, or floats. The function in add is type-neutral. This is *not* to be confused with duck-typing (Scheme is not duck-typed). There is a subtle point here that all functions in Scheme are type-neutral until they execute, but this is not what I am getting at (ignore that Scheme is dynamically typed for the moment). I concede that it is harder to type-check inferred-type methods in a statically-typed language like Java, but CICE makes this impossible without pre-defined generic types: I must declare this type simply in order to use it anonymously. This feels backwards to me. By contrast, this is a Haskell function: add x y = x + y Does add() take Ints? Longs? Floats? We don’t know until it is used. And we don’t care. Haskell functions are quantified over all types. In other words, they are type-polymorphic. Haskell is statically-typed, so this is not an apples-to-oranges comparison either (if you were wondering that it was with Scheme and CICE). The CICE proposal claims this is something that could be improved upon. More on type-neutrality OK, so you sort of grok that a closure is different from a CICE in that you don’t subclass a pre-defined type. But what’s so bad about that? One-method types are easy to create (and most useful ones already exist–example Callable<T> and Future<T>). Without resorting to duck-typing what else could I possibly think type-neutrality provides? “Real” closures possess function types. This essentially boils down to a tuple consisting of [R, A...An] where R is a return type and A…An are argument types. So what? Well–this actually means closures are quantifiable over all types matching the tuple (ANY matching function) and not simply a named type. Consider: As opposed to a real closure: Named types don’t enter into it. The closure’s type is captured over its functional type signature, which effectively gives it the flexibility of structure typing (but without contaminating the structuraltype system itself–unlike scala). Much more powerful indeed. Other stuff For these reasons I believe it is misleading to call CICE a “closures proposal”. It should certainly not be considered in the same vein as BGGA for instance. Note that none of this speaks to the merits of CICE as a code footprint reduction proposal. Nor to BGGA in terms of its merit as a closures proposal. I am not endorsing either proposal, nor condemning CICE as a language feature. I just want to highlight that it is inaccurate to equate CICEs to closures.bind(Service.class).toProvider(new Provider<Service>() {
public Service get() {
return new ServiceImpl();
}
});
bind(Service.class).toProvider(Provider<Service>()
{ return new ServiceImpl; });
public Runnable get() {
int x = 1;
Runnable r = Runnable() { System.out.println(x); };
x = 2;
return r;
}
public Runnable get() {
public int x = 1; //huh?
Runnable r = Runnable()
{ System.out.println(x); };
x = 2;
return r;
}
list.each({ it -> doStuffTo(it); });
(define add (lambda (x y) (+ x y)))
public interface Adder<N> {
N add(N x, N y);
}
public java.lang.Runnable get() {
return my.domain.Runnable()
{ System.out.println("Hi"); }; //error
}
package some.other.domain;
...
public { => void } get(boolean where) {
return where ? { => System.out.println("Hi"); }
: my.domain.Closures.bye() ;
}
Filed under: 1 | 4 Comments
Tags: closures, guice, haskell, lambda, scheme, type system
I think your definition of “closure” is a little more constrictive than the common definition: a function evaluated in an environment containing bound variables. CICE is not “misleading” by the latter definition.
Sure, it would be nice if you could always bind variables in the exact same way as the enclosing context, but what works for a dynamically typed functional language where everything is immutable doesn’t necessarily translate to Java. The different variable binding rules are there not because it would be technically infeasible to keep the same rules, but because it would be undesirable. The constrained rules prevent common programming mistakes that are inherent in making mutable local variables accessible from other threads and long after the closure’s enclosing method exits. Even BGGA has adopted a notion of restricted vs. unrestricted closures for this very reason.
For example, you’re first get() example won’t only not work–it won’t compile, which I think is a good thing.
In other words, we’re not trying to match Scheme. We’re trying to do what’s best for Java.
Bob,
There was no ‘dynamically typed functional language where everything is immutable’ mentioned in this post.
Scheme is dynamically typed but variables certainly are mutable. The designers have already thought about and solved all the difficulties of implementation. Haskell has immutable variables but is statically typed. Proper closures should work in any lexically scoped language.
Very interesting post. This is hardly the place but hey: Couldn’t sleep last night, here is the result
http://avrecko.blogspot.com/2008/04/vsci-yet-another-closures-proposal.html
+1 for Groovy and Scala