From: Chip Salzenberg <[EMAIL PROTECTED]>
   Date: Tue, 20 Jun 2006 20:59:45 -0700

   WRT exception handling, I think the lisp condition/handler model is a good
   starting point.  It's simple enough to explain and use, and static models
   can easily be implemented in terms of it.

Excellent; I'm sure you won't be surprised to learn that I agree.  ;-}

   But I really don't like one thing about the CL handler model: it conflates
   non-local transfers of control with "this exception is now handled".

FWIW, some pre-ANSI implementations did have a mechanism for marking a
condition as having being handled, but in the long run, I believe this
was considered not useful.  In particular, having a "stateless"
condition object (i.e. one that does not record its progress through the
signalling mechanism) makes it cleaner to resignal the same condition
object later on.

   So (1) every continuation invocation has to check to see whether an
   exception is live so it can be marked dead, which complicates what
   should be as efficient as possible, . . .

I don't understand this.  Is the exception itself a first-class object?
If so, then whether it's live or dead is up to the GC.  If not, then
users can't be allowed to get their grubby paws on it, lest they stuff
it away in some persistent data structure.  But probably I have a
fundamental misunderstanding here; maybe you meant "live" and "dead" in
a different sense?

   . . . and (2) creative condition handlers can't use continuations as
   an implementation tool.

I don't understand this either (I'm certainly planning on doing so), but
that is probably because you've already lost me.

   But I see a way out; see below.

   On Thu, Jun 15, 2006 at 12:03:56AM -0400, Bob Rogers wrote:
   >    3.  FWIW, the Scheme dynamic-wind feature requires an action to be
   > invoked when re-entering the context as well as leaving it.  But this is
   > probably not relevant, as a real Scheme implementation would probably
   > not need Parrot continuations or actions in any case.

   Huh, that's odd, coming from you.  Having just spent the better part of my
   evening wrapping my head around call/cc and dynamic-wind, I'm about to
   modify pdd23 to replace push_handler with:

push_handler??  I assume you mean pushaction?

       $P0 = newclosure sub_to_call_when_entering_scope
       $P1 = newclosure sub_to_call_when_leaving_scope
       $P2 = newclosure sub_to_call_when_scope_is_finally_inaccessible
       push_dynscope $P0, $P1, $P2   # [*]
       ...
       pop_dynscope                  # [**]

   So, having chosen Scheme as a good model for scope and continuation
   handling, wouldn't a Scheme compiler want to take advantage of that?

Good question.  ;-} I could be mistaken, but I said this because I
believe that Scheme has a different idea of continuation than Parrot.
In brief [1], if you rewrite call-and-return into pure
continuation-passing calling, it looks like a call into the sub followed
by another call to the continuation.  All of these can in fact be
implemented as tail calls, since there are no returns, and Scheme
stipulates that you *must* tail-merge where possible.  This means you
don't need a stack -- there is never more than one activation record at
any given time -- so all of the state required for a continuation can be
captured by a closure.  In the Lisp community generally, "closure" and
"continuation" are therefore often used interchangeably, though this
does sweep some distinctions under the rug.

   So all a CPS language really needs is support for closures.  Such an
implementation is truly and utterly stackless, which means that
dynamic-wind needs to keep its own stack explicitly, and similarly for
dynamic binding (which, IIUC, is generally implemented in terms of
dynamic-wind).

   Mind you, my knowledge of Scheme is purely theoretical.  In fact, I
hadn't even encountered dynamic-wind myself until just a few months ago.
So my guesses about what a Scheme implementer would or would not do must
be taken with a large grain of salt.  It might be possible to create a
conforming Scheme implementation without CPS, but on the other hand,
every time I encounter CWCC I learn something new, so what do I know?

   One question about push_dynscope, though:  Is the
sub_to_call_when_scope_is_finally_inaccessible called when the
Parrot_Context is reclaimed?  If so, why is that needed?

   And getting back to exceptions, I'm seeing something that's pretty much like
   the CL model, where the 'push_eh' opcode takes a _closure_, and the list of
   handlers is its own array in the interpreter, not in the generic control
   stack, and which is called at 'throw' time in the dynamic context of the
   'throw'.  For conventional static languages like Perl 6 (:-)), the handler
   would pretty much report that the exception was handled (e.g. with a
   'caught' opcode) and then invoke a continuation which had been taken by the
   Perl 6 compiler to point to the 'catch' code . . .

That sounds good to me.  The two-stack model (handler and control) still
has the problem [2] of keeping them in sync when unwinding, but I assume
that will be in your new PDD23 version.

   I've been trying to brainstorm a more detailed strategy for unifying
Perl 6 and Common Lisp condition handling, but I don't want to hold up
this post any longer.  It would certainly benefit from answers to any of
these questions.  (And a re-reading of S04 won't hurt, either.)

   Now how would a CL compiler use this?  I ask, and also answer:

   If dynamic-wind is implemented (see below), it seems to me that a CL
   compiler could wrap each handler in a dynamic scope in such a way as to trap
   a non-local transfer distinctly from a return, and in the former case,
   automatically invoke the hypothetical Parrot "caught" opcode.  So CL users
   get full CL semantics, and everybody gets a faster continuation.invoke()
   operation.

   It seems so obvious that I'd suggest that's how it works, except for some
   reason I can't fathom, CL doesn't support continuations...?

It's a matter of history and politics; in order to truly fathom it, you
had to be there.  I wasn't paying much attention at the time, though, so
I have to guess at some of the tradeoffs that were made, but I do know
that some half-dozen vendors were involved, and they all had major
investments in traditional stack-based implementations.  In addition,
Lisp had a bad rep (not altogether undeserved) for being slow and
memory-hungry, so the X3J13 committee was motivated to produce a
language that could run efficiently on traditional hardware.  So nobody
was willing to experiment with radical change to basic technology.  The
Schemers shamed us Lispers into full support for lexical scoping; that
was radical enough.

   And besides, Lisp has had THROW and CATCH since dinosaur days, which
can transfer control to any active stack frame.  Adding continuations
would have allowed transfers back into old frames, but the community did
not consider this feature worth the price.

   I even intend to use continations to implement THROW and CATCH; I
just won't be able to expose them to users via standard Lisp constructs.
So, yes, I could install the equivalent of an UNDO block around the Lisp
code that does whatever Parrot maintenance is required on the Parrot
exception object (which, it now occurs to me, may need to be distinct
from the Lisp condition object).  But would I really need to do anything
here?  If an exception is caught by Lisp, why would Parrot even need to
know?  S04 seems to require a great deal of bookkeeping for unhandled
exceptions, but would that necessarily impact Lisp handlers?

   >    . . . Lispers are people too.  :-)
   > 
   > You mean -+<{gasp}>+- I can come out of my closet now?

   Join us out here, in the *bigger* closet!

Hmm . . . I wonder where this *next* door leads . . .

   I'm glad you kept CL and Scheme in view.  After today, modeling
   continuations and condition handling without reference to Scheme and Lisp
   seems as foolish as modeling text searching without reference to Perl.

Well, the one thing that Parrot *must* do well is support Perl 6, so
deciding to ignore the Lisp experience and go a different way might have
been justifiable on that basis.  On the other hand, there is a *lot* of
Lisp experience -- ANSI condition semantics are the result of a decade
or so of experimentation across multiple implementations -- so I believe
that both Parrot and Perl 6 will be better off for at least considering
it.

   [*] I think a version of push_dynscope that accepts three sub labels
       directly could also be OK, but since often one or more of them will be
       NULL, it's probably a waste.

   [**] The opposite of "push" is "pop", dammit!  Not "clear".  Sheesh.
   -- 
   Chip Salzenberg <[EMAIL PROTECTED]>

Yay!

                                        -- Bob

[1]  I'm afraid I'm going to bore some people with this paragraph, and
     mystify others; my apologies to members of the former set, and if
     you're in the latter set, please prompt me for example code.

[2]  Alluded to on 12-Jun-06, in the paragraph "Actions are dynamic
     state that must nest properly ..."

Reply via email to