I totally forgot to mention this bit:
Transparency is fun, but you don't always need it. In many cases, it's
actively a bad thing, such as when you're making a closure that's
going to "travel the world" so to speak: It's going to run at a wildly
different time, by a completely different lexical context, probably in
a different thread.
That's where CICE comes in:
widget.addActionListener(new ActionListener(ActionEvent event) {
/* do something */
});
This is just simple syntax sugar for creating anonymous inner classes.
Break, continue, return, and 'throw' all change meaning when you
create one, just like they do now when you create an anonymous inner
class literal. The only difference we'll make with cice is that an
unqualified "this" will by default refer to the outer scope and not
the inner scope, as you rarely need the inner scope this. You can
still get it: "ActionListener.this" would refer to the inner this.
The transparent closures would be implemented via the same thing,
really: anonymous inner classes. Exceptions will be made transparent
just by throwing them (the VM doesn't care about checked exceptions -
it's the javac compiler that refuses to compile your code, so the way
around it is to just compile it). For break, continue, and return,
we'll have to use exceptions to clean up the stack just like BGGA
does. We don't have the luxury of adding a new top-level Throwable so
our transfer throwables will have to subclass Throwable. We get around
the potential migration issues here (code that 'catches throwable'
would also get in the way of outer return/break/continue) by
_requiring_ methods that want to take in closures to be specially
marked. As part of the contract of marking your method with "I take
closures here", you must ensure you don't eat the transfer throwables.
Thus, you could redefine Collections.sort as such:
public static <T> void sort(List<T> list, do Comparator<T> comparator)
{ /* code as if comparator is just a Comparator */ }
The "do" keyword used in front of the type of the second parameter is
the flag that says: I take closures here. As part of the deal, when
this code is compiled, the compiler (well, lombok's extension to it,
obviously), will check that 'comparator' is *NOT* allowed to escape.
You may not return it, you may not store it in a field, and you may
not pass it to another method, unless the method you're passing it to
is also marked as accepting closures. This way we ensure that the
closure cannot be used elsewhere. In the actual implementation of
Collections.sort, none of these rules would prevent it from being
compiled.
We *MAY* consider allowing some sort of 'disable these rules' hack.
That way you can actually move closures around. As long as you very
carefully transport all exceptions (including the transfer throwables)
back to the originating thread, there should be no problem. This would
allow you to write a Swing.invokeAndWait, or a parallel array
operation, via closures, eventhough you can't write those things
without running into this escape detection mechanism. It's a niche
feature though - other than Swing.invokeAndWait and parallel arrays,
there aren't many use cases.
For some closure usages, defining the closure as a new break/continue
context makes sense: For example, if you had written java5's foreach
syntax as a closure, clearly you would want "break" to break out of
this new for-each-like thing, and 'continue' to continue it. To allow
this, we were thinking of letting you write "for" instead of "do".
This does mean you can't tell in the code that declares a closure if
break/continue got a new context or not, so we're not 100% sold on
this particular one just yet. FCM's control abstraction addition
instead has a new for syntax that calls closures; we may instead go
with that.
For non-transparent closures (so, CICE closures), "return" is just
that: the usual inner return. The inner.return and outer.return syntax
would only be for the new transparent closures.
Maybe we'll even allow labelName.return, so that you can return 2
levels up in a stack of 5 nested closures, but that's kind of a niche
deal. I'd be allright if I can only return either from the innermost,
or the outermost, level, without the option of returning from a
closure in the middle of a bunch of nested ones.
It's fairly obvious that any given closure declaration needs a bunch
of meta-parameters, such as:
- is this a new break/continue scope?
- What are the semantics of the 'return' statement?
- Will this closure only be run with context intact (transparency
please!), or will it be escaping its stack context (no transparency!)
The question is: Where will this parameter be set: On the site that
receives the closure, On the SAM type that is backing the closure, or
where you write the closure?
Right now we have a sort of mixed bag here: break/continue scope is
set on the closure receiver site (via using the 'do' or 'for'
keyword), the semantics of the return statement are made invariant via
the 'inner' and 'outer' syntax, and whether or not the closure is
transparent is set at the site where you write the closure (Using a #,
or pipes, means: transparency. No hash means: no transparency).
Tagging the SAM types backing the closures is a bad idea, for the
reason you stated. Another nice example is Comparator: When you pass a
Comparator to a TreeSet, it's unsafe, but when you pass it to
Collections.sort, it's safe.
Perhaps we should endeavour for consistence here and either move all
these parameters to the declaration site, or to move them all to the
recipient. If it won't become too much of a chore to type, I prefer
having all this stuff on the declaration site, as that would improve
readability a bit - otherwise you'd have absolutely no clue if a
"return" inside a closure means an inner or outer return without
opening up the definition of the method that the closure is being
passed to.
So, everything-at-declaration means:
- The 'do'/'for' keyword syntax goes away, and instead if you want a
new break/continue context you'd have to write something like: "for #
(String a, String b)", or "for | | statements;". One could consider
moving the for away from parameters, so you'd get:
for Collections.sort(myList, | | {statements;});
But now you can no longer set different for/do parameters if your
method takes more than 1 closure. Python's for/else could not then be
written as closure, because the for block needs for semantics, but the
else block doesn't. Not the end of the world, but would be nice, and
more consistent with the other syntax suggestions, if this for/do
distinction is set on the closure itself, and not on the method call
that involves this closure.
- "inner.return" and "outer.return" stay.
- There's a forced syntactic difference between a CICE-like non-
transparent closure, and a transparent closure, most likely by making
either a # or a pipe symbol, depending on how we write up the syntax
for the closure's param lists, mandatory, as a flag to know which of
the two types you want.
On the other hand, moving everything to the receiver side, you could
theoretically make the syntax a lot simpler:
- The CICE-like syntax would be legal anywhere, and you automatically
get transparency if the method you're calling says that's okay.
- 'break' and 'continue' automatically change meaning depending on
what you're passing your closure too.
- 'return' will just do the right thing.
The more I think about it, the less I'm liking this 'receiver side
chooses' stuff: It would mean that a later change in the API can
totally change the meaning of your code, and the kinds of changes that
would occur won't always trigger obvious compile time errors. From a
readability standpoint, it really sucks too. If you see a closure and
aren't familiar with the method that's being called with this closure,
you'd be completely in the dark as to what's going to happen there.
With declaration-site-parameters, you may not know what the closure
will do to this block, but you do know the block will be called 0, 1,
or more times, cannot escape from its stack, and you know exactly how
the code inside the closure interacts with the code outside of it -
you know what break and continue would be doing, as well as what that
return statement means.
It's worth noting that BGGA messed this up; for/do semantics are
decided by the recipient, all closures are transparent but
nevertheless you can be prevented from attempting outer return/break/
continues by changing the declaration site's arrow symbol from => to
==>, and the semantics of the return statement are decided by the
syntax of the return statement itself. No consistency.