m.invoke() vs. m()

Osvaldo Doederlein opinali at gmail.com
Wed Dec 9 04:29:05 PST 2009

2009/12/8 Eric Jordan <ewjordan at gmail.com>

>  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.

I guess you did this only to explore the problem, because these wrapper
methods are completely unnecessary. A straight closure call, like
bar.doFoo(x), would be compiled by simple desugaring into
bar.doFoo.invoke(x). You are artifically binding closures to methods, which
forces closures to adopt all method resolution rules including shadowing and
overloading; this obviously doesn't work. The disambiguation (when necessary
- namespaces with a mix of closures and methods with same name) should be
resolved by javac, with new rules as necessary. Then javac emits bytecode
with a getfield that's hardwired to either Foo.bar or Bar.bar.

Your further, worse examples below are also in the same strawman category;
of course that's really bad but then it's just because you created this
closure->method translation, that was never suggested, and not necessary.

I have a better idea for everybody interested in this issue: let's pick some
simple program that makes abundant usage of closures in several scopes,
twist it to create naming coincidences, and we can analyze whether
transparent () calls creates any significant problem, i.e. if we need new
resolution rules that add significant user-facing complexity (repeating
again: extra complexity in javac's implementation has zero importance). If
this happens and we cannot find a solution with simpler rules, then I'll be
convinced that transparent calls are not a good idea.

And even in that case, I think this syntax facility is good enough to be
implement anyway. We can just issue a warning for any closure that shadow
other variables (of closure type or not) or shadow/overload any method
(especially with compatible parameter lists). The warning tells the user
"danger, this code uses new and complex resolution rules, if you're too dumb
to learn it, please rename this closure". BTW, I'm a frequent user of
Checkstyle/PMD/FindBugs and they have such warnings for much simpler
thnings, including many types of shadowing. I usually "fix" my code to
please the validation tools even when I don't think it's really confusing -
code conventions have big value in a team, and every time I remove shadowing
I usually enhance a identifier to be more specific, which is good.


> 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/20091209/7d83b3e6/attachment-0001.html 

More information about the closures-dev mailing list