Thanks for a very interesting post, Ben.

You did make one gigantic mistake though - your original java remarks
are completely wrong. Your snippet won't compile at all, and it'll
never print "012" no matter how you look at it.

You can't make the 'i' in:

for (int i = 0; i < 10; i++) { ... }

final. You could if you don't actually change i anywhere (INCLUDING
removing the 'i++'), but doing that would be completely pointless.

A few further remarks on your post inline:

On Jun 5, 10:57 am, B Smith-Mannschott <[email protected]> wrote:
> I suspect that if Java didn't require final i, the results would still
> be the same, because the implementation would still be forced to copy
> values into compiler-generated fields of the inner Runnable() class on
> allocation.

Nope, that 'baroque' suggestion you had that the compiler has to
silently promote i to a mutable object on heap is ACTUALLY WHAT WOULD
HAVE TO HAPPEN. This is exactly why 'final' is required, because then
the 'I'll just copy it' approach is indistinguishable from actually
capturing the variable itself. *ALL* closure proposals for java that
include modifyable locals (be it 'silently', be it with a warning, be
it only if you add 'public' or 'shared' or some other keyword) by way
of declaring that variable on the heap, effectively replacing 'shared
int x = 10;' with 'final MutableInteger x = new MutableInteger(10);',
if that helps clarify the situation.

Note that your other examples of other languages all work the same way
too. The compilers involved detect the sharing situation and
completely change how the variable is created and accessed, at all
levels (both inside and outside the closures). Exactly how else do you
think this is going to work (other than the oberon case, which really
does truly capture a stack-hosted variable)? Note that JVM rocket
scientists have already explained at length (and using mostly rocket
science) that actually adding support to access, let alone mutate,
local variables from inside closures is completely infeasible and
would be way slower than any emulation via heap objects injected by
the compiler.

NB: I'm sure Ben gets this, but we sort of glossed over an important
point here: That local variables in java are basically hosted on the
stack. Each thread has its own stack, and everything a method puts on
a stack just disappears immediately when the method exits. A stack
cannot continue to operate until that happens, in fact (you simply
CANNOT return control back to your caller until everything you did to
the stack is undone). Closures, which can be moved to another thread
and can outlive the method they were declared in, rather obviously
cannot share its host method's stack space, and as a result, it cannot
access any variables its containing method has on the stack either.


> What *exactly* is the closure capturing? The
> *variable* or the variable's *value*?

The variable itself, not the value. Period. Always. In any language,
or they're abusing the term "closure" even more than java already
does. Java, to be precise, has never strayed from this definition
either, and is not going to. Right now it doesn't really matter
because java's reaction to capturing a non-final variable is always:
Make it final first. And for final variables, the distinction between
capturing the variable and its value is moot.

> A few examples:
>
> [snip python's WTF way of handling variable capture in python closures]

Oh, hey, thanks for reminding me and sharing this one with the still
interested reader. You're quite right about python's strange behaviour
here. Osvaldo (and any other hot-heads who think this stuff is 'easy'
in other languages) - take note.

> In any event, Java is capturing a *copies* of the *values* of outer
> variables in its closure-like inner classes.

That's an implementation detail that is attempting (successfully) to
emulate the semantically correct notion of accessing the original
variable itself. The emulation works because captured variables are
always final (in java, at any rate), and with final variables, copying
them or accessing the original is indistinguishable.

> The trade-off in this case is that the inner
> procedure Foo can not outlive the call to Demo that created it.

Ah, but, this is where the discussion gets really interesting! There
are a few at this point mostly abandoned closure proposals and add-ons
to proposals out there which make a distinction between 'safe' and
'unsafe' closures. Primarily these terms refer to the notion that the
closure, if run at all, will only be run when the method that declared
them is still around on the same stack. The oberon limitation is
REQUIRED if you want to "long break", "long continue" or "long return"
out of a method. In most proposals (including BGGA's safe/unsafe add-
on) this concept is intended to be a compile-time consideration only,
with the class verifier not worried in the slightest, because if this
invariant is broken the JVM won't crash, you'll simply get a
TransferError exception.

However, if you do move this concept into the JVM itself, verified by
the verifier, you could in fact have your notion of "true" variable
capture.

To be perfectly clear, this is no where near project lambda right now.
Nevertheless it's interesting. And on a personal note I think we
should definitely be going there, mostly because you can do so much
fun stuff when you have that 'origin is still on the stack' guarantee,
such as long returns/breaks/continues, but also a form of exception
transparency: When the runtime stack and the lexical stack are
guaranteed to be synced up, you can catch exceptions thrown INSIDE
your closures on the OUTSIDE of them, just like you can e.g. catch
exceptions thrown inside the body of a for loop outside a for loop.

-- 
You received this message because you are subscribed to the Google Groups "The 
Java Posse" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/javaposse?hl=en.

Reply via email to