[Squeak-e] Are Handlers Dynamically Scoped? (was: Comments
on Lex's "Object as Capabilities in Squeak")
Allen Wirfs-Brock
Allen_Wirfs-Brock at acm.org
Sun Feb 2 20:23:53 CET 2003
At 01:48 PM 2/2/2003 -0800, Mark S. Miller wrote:
>At 12:28 PM 2/2/2003 Sunday, Allen Wirfs-Brock wrote:
>...
> > In the Smalltalk model the
> >handler is executed before the stack is unwound. This allows resumable
> >handles to be trivially implemented.
>
>Does it have any other virtue? If we didn't have resumable handlers, would
>there be any remaining reason to prefer this? Note that the issue isn't when
>they're executed, but when they're looked up. Of course, they can't be
>executed until they're looked up.
The obvious advantage is that the complete state of the computation is
still available during the evaluation of the handler. This is clearly
essential for resumption and is certainly useful for debugging. I can
speculate that there are non-debugging situations where it is useful for a
non-resuming handler to have the computation state at the exception point
available (or at least preserved) but I don't have a canonical example at
my finger tips. There is quite a few years of experience with
this mechanism in the broader Smalltalk community. Perhaps somebody out
there can provide a good example.
Stepping back, this appears to me to be a classic early/late binding
trade-off. The Java model binds the decision to discard the computation to
an early stage in the exception processing sequence. The Smalltalk model
defers that decision to a much latter point. As is usually the case, late
binding provides more flexibility but carries a price.
The Smalltalk model with resumable exceptions certainly feels more powerful
in a way that is consistent with the more dynamic nature of Smalltalk.
However, I don't know that I'm prepared to argue that resumable exceptions
are essential (rather than just useful).
> >I'm relatively confident that your CPS model could be extended to
> >accommodate the Smalltalk exception model although it might be some work
> >to do so.
>
>The only way I can imagine reveals that this semantics is indeed a case of
>dynamic scoping. The way I imagine:
>
>A continuation could have an additional method available,
>"getHandler: exceptionTypeOrSomething" that either already knows a handler
>for that exceptionTypeOrSomething, or asks its continuation. This
>non-destructive looking up the stack is simply a deeply bound implementation
>model of dynamic scoping. (Alternatively, the continuation could instead have
>a non-destructive "handle: exception" method which gets delegated back, but
>it amounts to the same thing.)
That is essentially one (well, actually two) way(s) to implement it. In
fact, the ANSI Smalltalk exception mechanism doesn't need to have any
particularly unique primitive support. All you need are closures, a
primitive unwind mechanism, single-use continuations ([^]), and a single
thread-local variable. The original Digitalk implementation used a shallow
binding technique. A thread local pointed to a liked list of active
handler states. Establishing a protected region adds a new element to the
head of the list. Exiting the protected region delete the head of the list.
Unwind protection is used to ensure the integrity of the list. Each list
element records the exception to be handled, the closure for the handler,
and the continuation used to terminate the handler. Signaling an
exception is a matter of searching the list for an entry that handles the
exception and resetting the list head before evaluating the closure. I
personally prefer this implementation technique over deep binding
techniques that probe the call stack to find handlers as it doesn't
require reification of the stack.
A reasonably high fidelity approximation of the Smalltalk exception system
can be implemented in Java. Java has unwind protection as well as thread
locals and Java exceptions can be used as the continuation
mechanism. Java don't have real closures but anonymous inner classes can
be used as an approximation.
If you accept that Smalltak exceptions can be implemented using the above
primitives then I'm not sure that you even have to explicitly account for
exceptions in your formal model (assuming that you do model the primitives).
>The key thing about the Java alternative, unwinding to the handler, is that
>the continuation is only ever invoked destructively, and is otherwise
>opaque. So by the time the handler is invoked, it's a handler associated
>with the immediate continuation, and not one retrieved from further back on
>the stack.
>
>So even without resumption, if earlier handlers get invoked before later
>unwind blocks, then I'd agree with Anthony that Smalltalk's exceptions are
>an instance of dynamic scoping. This isn't to say that it's a bad idea. But
>it does leave us with the following hypotheses:
Most of the legitimate uses of resumable exceptions that I know of do,
indeed, seem to be specialized examples of dynamic scoping.
>1) Resumable handlers are bad.
>
>2) Dynamically scoped resumable handlers (as in Smalltalk) are good, leaving
>us with at least one case where dynamic scoping is a good idea. If it's a
>good idea here, there are probably other cases as well.
>
>3) Resumable handlers are good, but a resumable handler shouldn't be looked
>up by dynamic scoping. (Note: Joule has lexical resumable handlers called
>"Keepers".)
>
>4) Terminating handler lookup should happen during unwind, like Java.
>
>5) Terminating handler lookup should happen prior to unwind, as in
>Smalltalk, making this handler lookup arguable another case of dynamic
>scoping.
>(In order to make this point separate from #2, let's say "should" even in
>the absence of the need to support #2.)
>
>6) Terminating handler lookup should happen prior to unwind, but not by
>looking up the stack. (Presumably, the alternative would be lexical. I know
>of no systems that do this.)
>
>Smalltalk: #2, #5.
>Java & E: #1, #4.
In the case of Java/C++, I'm not sure that #1 is the motivation for their
design. I believe that the Smalltalk style of handler would be difficult to
implement (or of very little utility) without first-class closures.
>Joule: #3. (Joule has no stack, and so can't have any conventional notion of
> termination.)
>
>Does this seem like a useful framework for exploring the issue?
Yes!
>What are some arguments for #2 or #5? I'm prepared to argue for #1, #3,
>and #4.
To some degree I've touched upon #5 issues above. I willing to make a case
for #2 but it will have to be in another message. I believe I share with
you the position that static (lexical) scoping is usually preferable to
dynamic scoping. However, I am interested in hearing why you may think
that dynamic scoping is never useful.
Allen_Wirfs-Brock at Instantiations.com
More information about the Squeak-e
mailing list