stack-bound vs. travelling closures proposal.

Reinier Zwitserloot reinier at zwitserloot.com
Wed Jun 16 23:52:16 PDT 2010


replies inline.

On Thu, Jun 17, 2010 at 6:21 AM, Brian Goetz <brian.goetz at oracle.com> wrote:

>
> Having said that, I am not convinced it is the right vehicle for exception
> transparency, because it violates a basic principle: that method signatures
> declare the checked exceptions they throw.  (Yes, I know about many ways
> exceptions can be thrown sneakily.)
>
>
Hmm. How crucial is this idea? The straw man proposal doesn't actually
involve closures declaring what exceptions they throw; it's inferred.
Closures aren't method signatures, but, they're close to it.

The mental thought process here is that the e.g. "forEach" method does NOT
in fact throw that exception. It's the closure, which is transferring the
exception back to its declaration site, passing through all intermediate
stack frames as it goes. A rhetorical point perhaps (technically there's
certainly no way to tell the difference). Nevertheless, there's an
alternative that avoids the need to silently transparently propagate
exceptions from a closure's execution back to its declaration site, later in
this post.

And the restricted-ness for a closure argument has to become part of the
> method signature.  And switching a closure parameter between restricted and
> not becomes a binary-incompatible change.


Your follow-up post's correction also means that changing a parameter from
normal to restricted is backwards compatible. Going back from restricted to
normal wouldn't be. This is a major argument in favour of the
restricted/portable proposal. Comparator can never be retrofitted with a
"throws E". Collections.sort() CAN however be retrofitted with an "I take
restricted closures" flag. The number of SAM types that are going to have to
fall by the wayside with a @Deprecated mark on them, along with the
overloading of common java runtime library methods, is rather large if
Comparator has to then be retired in favour of #int(T, T)(throws E).
restricted/portable for exception transparency works in my opinion far
better with existing java code. No need to retire Comparator yet still able
to add exception transparency to Collections.sort.

So, are you basically saying that "for methods taking restricted closure
> parameters, the method is considered to throw any exceptions that might be
> thrown by the restricted closure(s)."  But this isn't embodied in the method
> signature; its as if the method does not have a choice whether to catch or
> expose those exceptions.


But how would this concept work, in practice, with the strawman? If the idea
is that a certain closure may freely throw IOExceptions, and they will be
handled by the method, then, either proposal has an obvious and
uncomplicated solution to convey this: Have the function type (or the SAM's
one method) actually include "throws IOException" in its signature. Once a
closure can itself legally throw an exception, exception transparency is no
longer needed.

The alternative is working with abstracted exception types, but that cannot
possibly work either. How would you even do that? "catch (T foo)" does not
seem to be possible given the concept of erasure. The only pragmatic thing
one might catch, then, is Throwable or Exception, which one can also do with
the restricted/portable proposal.

Said differently, "public <throws E> void foo() throws E" is not much better
than "public void foo() throws Throwable" for all code that hasn't bound the
E to something, which is a lot of code, exactly the code we're talking about
in fact (code on the stack in between execution and definition of the
closure).

Many of the arguments I've heard so far against portable/restricted aren't
realistic in strawman either.


>  You might call this "forced exception transparency", since methods like
> sort() are forced to expose any of the exceptions that might be thrown by
> their closure arguments (even if they intend to catch them.)
>
>
Not necessarily - one could also catch all of them, and document that this
happens. The true point is that restricted closures must be presumed to be
capable of throwing anything, but, then, isn't the same true of a closure
with signature "throws E"? That E could stand for anything. One of the
aspects lost by portable/restricted is that effectively the bound on E is
always "Throwable", whereas in the "throws E" syntax it is configurable. How
much use will the ability to set E's bound to something other than a
catch-all be? I can't think of very many cases where actually specifying
that exception verbatim ("throws ActualType" instead of "<throws E extends
ActualType>") is not sufficient. The biggest hole in portable/restricted in
this matter is that its not possible to declare that you catch all
exceptions. In strawman you can do:

public <throws E> void executeAndEatAllExceptions(#()(throws E) closure) {
    try {
        closure.();
    } catch (Exception e) {}
}

and from the signature it is clear. In portable/restricted:

public void executeAndEatAllExceptions(do #() closure) {
    try {
        closure.();
    } catch (Exception e) {}
}

this is not clear from the signature. However, given the lack of reification
of the E in "throws E", it's always all or nothing: Throw onwards everything
the closure declared, or, eat all of them. Is the ability to declare eating
all exceptions worth all that boilerplate and complexity?


> This is a pretty shaky argument.  Theft happens in the real world; the cure
> is not to declare that property rights are an untenable abstraction.
>
>
This analogy would suggest adding a verifier check to the JVM itself to
crosscheck the type at the top of the stack when an ATHROW opcode occurs
against the exception handler tables and a method's throws clause, thus
immediately causing serious problems for most of the alternative languages
on the JVM. However, I don't think that's feasible or desired.

Nevertheless, rolling with it: In the BGGA proposal, non-local transfer was
handled by throwing a Transfer throwable, which is then caught in the same
scope the closure is defined, and turned back into an actual break, letting
the exception clean up the intermediate stack. The same trick can be used to
handle transparent exceptions WITHOUT requiring intermediate code (all stack
frames in between the closure's definition and the closure's execution) to
take care to not catch any such exceptions - wrap the exception into a
special "Transfer"-esque throwable which are unpacked at the declaration
site. I'm not sure this extra complication is required, but it would
certainly help avoid issues where intermediate code accidentally catches
exceptions that it wasn't meant to catch. I'm mildly in favour of not
wrapping them (overkill), but there's plenty of benefits to doing so.
Methods that actually _want_ to intercept exceptions thrown by a closure can
simply catch the transfer, for example. It's certainly safer.


More information about the lambda-dev mailing list