m.invoke() vs. m()
Reinier Zwitserloot
reinier at zwitserloot.com
Sat Dec 5 07:51:51 PST 2009
Your idea is not going to work, Ricky.
I can come up with a gajillion examples where your idea is either ambiguous
or requires arbitrary ordering rules, and which would either way, as you so
nicely put it, lead to a language that is 'deliberately hard to follow'.
Problem #1:
public class Test {
private #()void x = #() {print("1");};
private void x() {print "2";}
public static void main(String[] args) {x();}
}
What does this print? 1 or 2? Both 'x'es are in the same lexical scoping
level.
Problem #2:
public class Test {
public static void main(String[] args) {OtherClass.x();}
}
The only way you're going to make #2 work is by making the very meaning of
this class change considerably depending on the structure of OtherClass.
This is a very bad idea; instead of simply asserting that a language feature
leads to confusion, here are the problems with that: (NB: gut instinct? Gut
instinct has no place in discussions about language features. Please give
actual reasons next time!)
Code should remain generally parsable and resolvable even if there are
syntax errors in it. This helps the compiler give error messages that state
the problem clearly and point in the right locality, and it gives people
writing code in IDEs a less jarring experience. For example, in eclipse,
problems with an inner class sometimes blow up the entire local namespace
resolution, meaning auto-complete doesn't work anymore. That should be
avoided if at all possible. Having such far-reaching dependencies on other
codes is a very bad thing for this concept; an error in OtherClass will now
have a big impact on our code, and may lead our code to parse completely
differently.
Problem #3:
It would also lead to problems with private API. Right now, java will
resolve presuming all members are public, and then generate an error later
when it realizes the method/field you're targeting is not visible. This
isn't just a common strategy amongst java parsers, but it's a useful error
mechanism as well; it catches problems where the code writer is confused
about access levels with a clear error message (That method isn't visible,
instead of That method doesn't exist). Your suggestion would mean that
changing the type of a *private* field from a non-closure type to a
closure-type has an impact on other code.
Problem #4:
Method Resolution is already one of the most complicated aspects of java,
and you're suggesting that we make it even more complicated. I don't think
saying that complicating the already extremely complicated is mere gut
instinct.
Right now, any parser that sees:
ident(args);
knows that this must mean: method invocation. It can tackle resolution by
first trying to match 'ident' to the method namespace. Thus, if ident here
is actually "x.y.z", then the compiler knows to look for a method named 'z'
in a type 'x.y'. It'll have to consider both local variables and the global
type namespace for 'x.y', which is already quite complicated, but at least
it knows that the 'z' MUST be a method name.
Not so in your proposal. In your proposal, we go from merely needing to sort
out the method namespace in 'x.y', to knowing far, far more about x.y:
- The entire variable namespace.
- The TYPES of the variables in x.y.
Type resolution is complex, and slow. IDEs avoid it as much as they can. If
you turn type resolution on for all types in IDEs, they become slow as
molasses even on multicore monsters.
Solution:
c.invoke(args) is the only legal closure invocation syntax. c(args) isn't.
Is 'invoke' some serious curseword in programmese? Does it offend you so
that this syntax just cannot stand?
--Reinier Zwitserloot
NB:
Backwards compatibility is: Code written for javacX in mind still compiles
into a class file with the same meaning when compiled in javacY, where Y is
greater than X.
Migration compatibility is: A class file produced by javacX still runs, and
does what its supposed to do, on JVM Y, where Y is greater than X.
The biggest issue here is most likely backwards and not migration
compatibility.
On Sat, Dec 5, 2009 at 3:04 PM, Ricky Clarkson <ricky.clarkson at gmail.com>wrote:
> Whereas the only interpretation that actually makes sense is to choose
> the closest in lexical scope. In other words, f.invoke(args) and
> f(args) should never do different things to each other if you support
> the latter as shorthand for the former, unless you're deliberately
> making a language hard to follow.
>
> That is just as backward compatible, as no existing code contains
> closures, so no existing code's semantics is altered. I don't quite
> know what migration compatible means; I guess it's a woolly term.
>
> 2009/12/5 Reinier Zwitserloot <reinier at zwitserloot.com>:
> > Yes, I presumed Mark screwed up on that. The repercussions of being able
> to
> > invoke any variable of a closure type simply by tossing parentheses after
> > its mention are rather staggering. The closest you can get to something
> that
> > is backwards and migration compatible and doesn't completely change the
> > entire language is something like:
> > when the parser sees something of this form:
> > identifier(argslist[opt])
> > the parser will consider this a MethodCallOrClosureInvocation.
> > During resolution, if identifier refers to a method name that is in
> scope,
> > then this is a method invocation. If there is no such method name but
> there
> > is a variable with that name, then it is a closure invocation.
> > Meaning, if you create a new method, or one of your supertypes adds a new
> > method, or something is statically imported, then a closure invocation
> can
> > silently turn into a method invocation.
> > The fun quadruples when you try to add some rule to the tune of: if
> > 'identifier' is a legal method but the argslist doesn't fit any of that
> > method's overloads, but it does fit the closure, then presumably, execute
> > the closure instead. Yich.
> > That's rather a lot of dancing the parser/resolver has to do in an area
> > that's already extremely complicated (method resolution), all to avoid
> > having to write '.invoke' in there. I'm not a fan of frivolous syntax
> merely
> > to highlight something that your IDE can already tell you (that
> 'identifier'
> > is a variable with a closure type), but in this case, the '.invoke'
> actually
> > does impart something of significance that your parser/IDE cannot always
> > infer for you: That the thing to the left of the invoke is a variable and
> > not a method name.
> >
> > --Reinier Zwitserloot
> >
> >
> >
> > On Sat, Dec 5, 2009 at 1:33 PM, <tronicek at fit.cvut.cz> wrote:
> >>
> >> Hi,
> >>
> >> I noticed a discussion about m.invoke() and m() on Mark Reinhold's blog
> >> and although it is true that if m is in the variable name space, we need
> >> "invoke", if syntax
> >>
> >> fun void m(int x);
> >>
> >> is adopted, it seems natural to have m in the method name space and then
> >> the "invoke" is not needed.
> >>
> >> Z.
> >> --
> >> Zdenek Tronicek
> >> FIT CTU in Prague
> >>
> >>
> >>
> >>
> >>
> >
> >
>
>
>
> --
> Ricky Clarkson
> Java and Scala Programmer, AD Holdings
> +44 1565 770804
> Skype: ricky_clarkson
> Google Talk: ricky.clarkson at gmail.com
> Google Wave: ricky.clarkson at googlewave.com
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.openjdk.java.net/pipermail/closures-dev/attachments/20091205/f265bde5/attachment.html
More information about the closures-dev
mailing list