From: Allison Randal <[EMAIL PROTECTED]> Date: Tue, 23 Oct 2007 23:11:51 -0700
Bob Rogers wrote: > > All I am talking about is the equivalent of what "local $var" provides > for Perl 5, i.e. dynamically-scoped binding of scalar "package > variables." Perl 5 locals and Perl 6 temps are quite different than Lisp special variables. I don't immediately see a way to explain this any more clearly than I explained it last time. You did explain it clearly; that's why I said "scalar package variables" (and didn't mention "temp" except to disavow it). AFAICS, these have semantics nearly identical [1] to Lisp special variables, and I don't see anything in last fall's discussion to the contrary. Much of that discussion was spent curing my ignorance of the difference between binding and assignment, and how that applies to "temp", plus other discussion related to localizing/temping registers and structure members, but I thought I stated clearly that I was not going to consider such things. > If the problem is simply "implement Common Lisp special variables", then > the most likely solution is to create a LispSpecialVar PMC. In the same > way that a MultiSub acts like a sub but internally stores a list of > subs, the LispSpecialVar would act like an ordinary variable to Parrot > internals and to all other languages, but would internally store a stack > of previous dynamic bindings. > > How would continuations capture dynamic bindings, then? What information do the continuations need to capture? i.e. what do they need to restore when invoked? In general, continuations capture globals, but don't capture the values of the globals. Invoking a continuation doesn't restore the value of a global. In a nutshell, a "special" binding changes the dynamic environment, in the same sort of way that pushing an error handler does -- both are inherited by subs called in that environment. Exiting from that environment, by whatever means, must return to the previous environment at the point where the continuation was taken. In fact, if Parrot were to provide an implementation of dynamically scoped global variables in which invoking a continuation did *not* fully restore them, then I would not be able to use such an implementation. If I tried to, the nonlocal transfer-of-control features of Lisp (implemented with continuations) would break [2]. This should not surprise anyone. (Does it??) One such nonlocal transfer-of-control feature is "catch/throw". Here is some Lisp code (albeit somewhat contrived) that illustrates the matter: (defun main () (format t "*print-base* is ~D.~%" *print-base*) (let ((*print-base* 16)) ;; Because I want output in hex. (format t "*print-base* is now ~D.~%" *print-base*) (catch 'done (let ((*print-base* 2)) ;; Well, I really want it in binary. (format t "*print-base* is now ~D.~%" *print-base*) (dotimes (i 16) (format t "~S~%" (generate-row i 0)))) (format t "We never get here.~%")) (format t "*print-base* is still ~D.~%" *print-base*))) (defun generate-row (i j) (cond ((> i 5) ;; Gratuitous nonlocal exit. (throw 'done nil)) ((>= j i) nil) (t (let ((*print-base* j)) ;; Since nothing is printed in this dynamic scope, ;; the only effect is to create a binding that must ;; be popped when exiting this frame (or throwing ;; through it). (cons (* i j) (generate-row i (1+ j))))))) *print-base* is a global variable that controls the output radix in the obvious way, and it's initial default value is 10 (decimal); all of the numbers in the source code are decimal. This global is rebound twice in the "main" function, and once in the generate-row function. The special "throw" operator transfers control to the innermost "catch" with a matching tag, unwinding the dynamic environment to the point of the catch, where "dynamic environment" means the state of all error handlers and dynamic variable bindings [3]. The output looks like this (where "PARROT" just happens to be the name of the package I'm in): PARROT> (main) *print-base* is 10. *print-base* is now 16. *print-base* is now 2. NIL (0) (0 10) (0 11 110) (0 100 1000 1100) (0 101 1010 1111 10100) *print-base* is still 16. NIL PARROT> On the sixth time through the loop, "throw" has to unwind out of 6 stack frames (and the *print-base* bindings in 5 of them) to get back to the "catch", but must unwind only the innermost of the two *print-base* bindings in "main". As I said above, "catch" and "throw" (along with the lexically-scoped nonlocal transfer-of-control primitives of Lisp) are implemented in Kea-CL using continuations; indeed, I don't see how I could do otherwise. If each dynamic variable binding is stored as an entry on interp->dynamic_env (as I did in last fall's attempt), then invoking the continuation automatically restores these bindings to the right place -- and ditto for exception handlers, which are basically continuations. > How would > stack-unwinding know which dynamic bindings to unmake? A LET has a scope (a beginning and an end), so you generate the binding code at the beginning and the corresponding unbinding code at the end. Allison That works fine when the LET is exited normally, and such unbinding code is needed for that case. But what if an error or nonlocal exit causes the unbinding code to be skipped? How would Parrot figure out which bindings need attention, and what values to restore? From re-reviewing our earlier correspondence, I get the impression that you have a particular implementation in mind, one which doesn't seem to work for my use case. Perhaps you should describe the use case for your implementation, and then we can decide whether to combine them. -- Bob [1] The differences don't matter at this level of discussion, it just means I'll need a little extra mechanism for Lisp. [2] In fact, catch/throw only works now without an implementation of dynamic variable scoping because I kludge around it with C<pushaction>, as mentioned on 7-Dec-06 in last fall's thread, and continuations (now) do the right thing (mostly) with C<pushaction>. [3] Plus, strictly speaking, a few other things that don't matter in practical terms, because they are typically implemented using dynamically bound globals, so they don't require special handling. (In Kea-CL, even catch & throw are implemented using a dynamically bound global.)