From: Larry Wall <[EMAIL PROTECTED]>
   Date: Mon, 2 Jan 2006 20:11:23 -0800

   On Mon, Jan 02, 2006 at 06:55:24PM -0500, Bob Rogers wrote:
   : [2]  About two-thirds of the way through A06 (search for "temporize
   :      object attributes"), Larry says that this will be done via
   :      closures.  In order to support rezipping, such a closure would need
   :      to accept a new value to store and return the old value.  Or maybe
   :      the .TEMP method could just return a location PMC?

   I would prefer the latter for the general case.  As with any "rw"
   location, you're returning a proxy/location object with an appropriate
   lvalue interface.  Any user-specified closures end up being methods
   of that object.  Treating such a closure as the proxy object not
   only confuses getting with setting but also confuses both of those
   with the original identification of the location.  I was not terribly
   consistent in carrying this idea though in the original Apocalypses.

Thanks for making that clear.  The advantage of supporting closures, of
course, is that they are very general, and allow pretty arbitrary things
to be rezipped (if you want to allow such extension).  But I do agree
that it's better to use something that describes exactly what you mean,
and is (at least potentially) introspectable.

   By the way, it's getting to be a bit dangerous to quote the Apocalypses
   rather than the Synopses, since they've diverged somewhat, and the
   Synopses are being kept rather more up-to-date.  The "post" block
   has turned into a "leave" block, for instance.  (Pre/post blocks are
   reserved for Design-By-Contract now.)

Sorry; I was hoping to cut down on the amount of reading I need to do.
I still haven't even covered the full set of Apocalypses.  I've also
avoided subscribing to p6l for the same reason.  But I suppose I need to
reconsider both.

   Anyway, I think we'll need to recognize over the long haul that some
   forms of control flow are simply incompatible with certain kinds of
   state change, in a Haskellian monadic sense.  I think if the first
   whack at rezipping can simply detect such situations and refuse to
   proceed, we'll at least be up to the Perl 5 level, even if we can't
   continue into a temporized area.  After all, the entire Perl 5 model
   assumes that you only have to undo once.  

Yes; "life was much simpler back then . . ."  Closures can be a pain,
but on the other hand, they are also a lot of fun; personally, I
consider them the natural vehicle for implementing nonlocal transfer of
control.  Which is why I care about getting things to rezip correctly.

   The good news, though, is that I haven't seen mention of anything
that I wouldn't know how to implement reasonably -- with the exception
of "Hypotheticality and Flight Recorders", which itself seems rather
hypothetical.  But even there, a lexical version might be a reasonable
thing to consider:

        $x = 0;
        temp { 
            $x = 1234;
            print $x;       # prints 1234
        }
        print $x;           # prints 0

That pushes much of the work back on the compiler (which needs to run
wires into the flight recorder), but that strikes me as reasonable; you
don't need or want EVERYTHING captured by a flight recorder.

   But all that can wait.  The point is that I think the proposed model
is robust enough for what you want to do with it (caveat my limited
understanding of same).

   It'd be nice to generalize
   from that, but Perl 6 is moving more toward using a thing we're calling
   "environmental" lexical variables to represent dynamic context anyway,
   which I think is more like what you'd call a deep binding.  So from
   a Perl perspective, temporization is being de-emphasized somewhat.

FWIW, the terms "deep" and "shallow" binding are old Lisp terms (in case
you hadn't already guessed), and as used in the Lisp community, they
refer to dynamic binding exclusively.  (For this purpose, deep binding
is clearly less efficient, which is why you don't hear those terms used
much at all any more; shallow binding is taken for granted.)

   But in any case, I do not understand this bit, which is probably a
reflection of the Synopsis reading I have to do.

   On the other hand, as you point out, you do have to be careful about
   unwinding exceptions.  We'll need to do that as lazily as possible,
   since some exceptions will include an optional continuation to
   resume if the exception thrower wants to allow the exception to be
   "defatalized".

That allows only one way to continue, though, and it's under exclusive
control of the exception-thrower.  Have you encountered the Common Lisp
'restart' concept?  (The reference is [5], but there's a fair amount to
it, so I won't try to produce a capsule summary.)  The advantage of
restarts is that they allow users (both throwers and catchers) to define
named restart actions that can be invoked programmatically, or
interactively via the debugger.  Specifying that a particular action
should happen whenever a given error is encountered then becomes a
simple matter of binding a handler that invokes the right restart, which
could even be done by a third party (i.e. neither the thrower nor the
catcher).  But only certain predefined restarts are possible, which
makes the exception handling process a multiway negotiation where no
party has full control.  (IIRC, the original paper [6] makes this
distribution of control an explicit goal of the design.)

   In fact, I suspect we'll end up handling all warnings as exceptions
   that are defatalized by default by an outermost warning exception
   handler, and it'd be a shame if, when we resume after a warning, the
   temporizations have all been undone.  Even if they get rezipped at
   resumption, that's gotta be a big performance hit just to emit a
   warning.

But that argues that warnings should be handled close to the point that
the warning is emitted, at least in the case where all that is required
is to suppress them.  I can't resist quoting the entire WARN definition
from the CMUCL implementation, which also serves as a good example of
using restarts:

        (defun warn (datum &rest arguments)
          "Warns about a situation by signalling a condition formed by datum and
           arguments.  While the condition is being signaled, a muffle-warning
           restart exists that causes WARN to immediately return nil."
          (kernel:infinite-error-protect
            (let ((condition (coerce-to-condition datum arguments
                                                  'simple-warning 'warn)))
              (check-type condition warning "a warning condition")
              (restart-case (signal condition)
                (muffle-warning ()
                  :report "Skip warning."
                  (return-from warn nil)))
              (format *error-output* "~&~@<Warning:  ~3i~:_~A~:>~%" condition)))
          nil)

The SIGNAL call is what gives handlers a chance to run.  In the normal
case, nobody handles it, and control drops through the RESTART-CASE to
the FORMAT, where the warning is printed.  (SIMPLE-WARNING is not fatal
because it is not a subclass of ERROR, so the default debugger handler
also gives it a pass.)  On the other hand, if somebody binds a condition
handler to SIMPLE-WARNING that does "(invoke-restart 'muffle-warning)",
then the restart does a RETURN-FROM, and the output is skipped.  Since
the handler is a closure, the code can examine the contents of the
condition object before deciding to muffle it.  And all of this happens
in the dynamic context of the WARN function (inside
kernel:infinite-error-protect, which sounds like a really good thing).

   So maybe there's some way of running CATCH
   blocks in the dynamic context of the thrower without rezipping twice.
   Have to think about the semantic ramifications of that though...

   Larry

I'm not sure that the cost would be that large; we're only talking about
tens of state-capture PMCs here.  (I certainly hope.)

   But in any case, rezipping may not be required.  At the risk of
trying your patience, I will lay one more bit of Common Lisp
error-handling semantics on you, as I hope it will serve as food for
thought.

   The CL error signalling process [7] has two phases:  The first phase
decides which handler takes the error, and the second actually does the
job.  This is supported by two syntactic abstractions:  HANDLER-CASE
works like try/catch, and is typically implemented in terms of
HANDLER-BIND, which provides the lower-level but more general mechanism.
Sweeping much under the rug here, the low-level handlers are closures
that are run in the dynamic environment where the error is signalled; if
they decide to handle an error, they do so via a nonlocal transfer of
control back to the right handler in the HANDLER-CASE environment.

   The upshot is that the bound handlers can be made pretty light-weight
and therefore insensitive to the dynamic environment in which they run
(unless, of course, they need to be), without causing the author of the
HANDLER-CASE code to lose control of the environment in which the error
cleanup forms run.

   So if you would consent to putting the "when" guard tests of each
"catch" in closures, with the caveat to the user that they could be
evaluated an arbitrary number of times in somebody else's backyard,
Parrot could always find exactly the place to which it's going to unzip
before it starts.

   And this leads to the biggest win of the "know before you throw"
approach:  You find out about unhandled errors immediately, before
you've destroyed the most interesting part of the stack.  That is the
chief drawback of the current Parrot model; I've been ruminating about
it for a while now, but have no concrete proposals yet.  I'll bet that
wrestling with dynamic binding will lead me to generate one (or two) --
if you or someone else doesn't beat me to it.

   Thanks for speaking up, and thanks especially for listening,

                                        -- Bob

[5]  http://www.lispworks.com/documentation/HyperSpec/Body/09_adb.htm

[6]  http://www.nhplace.com/kent/Papers/Exceptional-Situations-1990.html

[7]  http://www.lispworks.com/documentation/HyperSpec/Body/09_ad.htm

Reply via email to