m.invoke() vs. m()

Eric Jordan ewjordan at gmail.com
Tue Dec 8 14:12:01 PST 2009


 Neal Gafter wrote:
> Things are not that simple.  What about nested scopes,
> or the scope of a class/interface versus its subclasses/subinterfaces?
> Hiding?  Shadowing?  Overloading?  These all have to be
> worked out.  I think it's worth doing, but it's not trivial.

Just as a start, I want to pin down some of these things - as you've said,
this is not nearly as straightforward as it seems at a glance, and I think
it will help everyone to see exactly why.  I myself thought "well, just put
closures into BOTH namespaces" was enough to settle it, but realized that
it's easier said than done.

My first thought was that any class with a declared closure member:

class Foo {
  #(arg types)ReturnType doFoo;
}

could be rewritten to an equivalent class with the "default" Java namespace
rules (where closures are objects, not methods) that simply delegates the
call to ".invoke":

class Foo {
  #(arg types)ReturnType doFoo = (do something);
  ReturnType doFoo(args) { doFoo.invoke(args); }
}

That would give us the desired doFoo() syntax and piggyback off of the usual
method resolution rules, seemingly giving us "obvious" behavior and error
messages (which, even if unaltered, would still make sense since the
generated method name matches the closure) for free, including
inheritance/hiding/overloading, behavior in inner/anonymous classes, etc.

Let's see what happens if we consider a second class that would usually
shadow the closure:

class Bar extends Foo {
  #(different arg types)ReturnType doFoo = (do something else);
  ReturnType doFoo(different args) { doFoo.invoke(args); }
}

Even though the closure is shadowed and replaced with a different signature
closure with the same name, calls to doBar with arguments matching either
the visible or the shadowed closure will pass to the correct closures and
complete as usual - this could be either a positive or a negative, as it's
now easier to call Foo's closure with the () syntax than with .invoke
because we can omit the otherwise necessary cast, but that's kind of an odd
quirk.  Welcome to the difference between methods and members - as far as
shadowing is concerned a method's signature is part of its name, so we can
overload, whereas for variables having different types doesn't prevent
shadowing.

This goes deeper.  With the closure equivalent of a typical method override,
we'd have

class Baz extends Foo {
  #(arg types)ReturnType doFoo = (do something weird);
  ReturnType doFoo(args) { doFoo.invoke(args); }
}

When called with (), Baz's doFoo will be used in preference to Foo's doFoo.
 The closure inherits the method polymorphism when we use ().  However, when
we invoke the closure directly, that's not the case, because direct variable
access is based on the declared class of the reference, not the runtime
class.  In other words, there's now going to be a difference depending on
how our reference looks:

Baz myBaz = new Baz();
Foo myFoo = (Foo)myBaz;

myBaz.doFoo(args); //Baz's doFoo
myFoo.doFoo(args); //Baz's doFoo

myBaz.doFoo.invoke(args); //Baz's doFoo
myFoo.doFoo.invoke(args); //Foo's doFoo

Oops.  That's bad.  Take a closer look if it doesn't bother you:
myFoo.doFoo(args) does something different than myFoo.doFoo.invoke(args),
and that's a direct result of Java's (pretty much unchangeable) rules about
method and variable overriding/shadowing.  That's why it's not quite as
simple as "putting closures into both namespaces", the lookup rules are
different in each one.

Wanna take bets on whether *that* one's showing up in the post-closures
round of Java-gotchas if the syntax works like this?

Deciding what we even *mean* by myFoo.doFoo when doFoo is a closure and
myFoo has subclasses (where doFoo might be defined as well, either with the
same or different signature) depends on whether we take a closure's variable
nature or its method nature more seriously.  We just can't have it both ways
in the face of possibly shadowed variables.

If we really want () to be equivalent to syntax sugar for .invoke, the right
thing to do would be to make sure that polymorphism does not apply to
closure calls.  That's probably possible, but it's not as simple as a quick
source-rewrite rule, so I haven't worked out much about how it would work.
 And I haven't even started thinking about local variable closures,
exceptions, or many other things that might complicate matters.

So I'm not saying that this is impossible (and I still think it's worth some
effort to figure out), but it's definitely not a problem with an obvious,
simple solution.

- Eric
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.openjdk.java.net/pipermail/closures-dev/attachments/20091208/52e5a04b/attachment.html 


More information about the closures-dev mailing list