From: Allison Randal <[EMAIL PROTECTED]> Date: Sat, 09 Dec 2006 21:55:35 -0800
Bob Rogers wrote: > This is also my definition of "dynamic scoping". Were you not aware of > this behavior of "local", or are we talking about different things > entirely? I was talking about the lifetime of the temporization not static vs. dynamic scoping. OK, then I think we agree. (Your original statement was ambiguous about whether you were talking about scope or lifetime, but then your next statement used the word "scope.") But, this does help me better understand what you're getting at. The proposal only mentions "dynamic scope" three times, but reading through it again now I can see that the whole proposal is a literal implementation of a classic textbook definition of dynamic scope. Yes. But I've been calling it "dynamic binding" because it's the variable binding that has dynamic scope. I'm willing to adopt your terminology, but you'll have to bear with me, because I've been saying "dynamic binding" for quite a while now. But what would you have me use for the verb, if not "to bind"? [For reference, Wikipedia says: "In dynamic scoping, each identifier has a global stack of bindings. Introducing a local variable with name x pushes a binding onto the global x stack (which may have been empty), which is popped off when the control flow leaves the scope. Evaluating x in any context always yields the top binding."] I saw this, too. I assume this is just a conceptual simplification for teaching purposes; it would be silly to create a literal stack for each variable when only a handful of them will ever be rebound this way at any given time. None of the compilers I'm aware of do it this way. > But I'm still unclear how this distinction is made in a Perl 6 > program. If you write > > temp $foo = 37; > > is this assignment or binding? And in either case, how do you specify > the other? Larry? (Not that understanding this is likely to be > necessary for getting Parrot to do the right thing.) That's assignment. Binding would be: temp $foo := 37; Great; thanks. > > Could you be more specific about the proposed implementation? What > > issues do you have with it? Is it worth fixing, or should I give up? > > Mainly, I don't see the proposal as it stands as a solution for 'temp' > and 'local'. Dynamic binding as you define it (what I'm calling > thread-locals), is a completely different problem. The proposal needs a > description of the problem it is solving, instead of describing one > problem and solving a different one. > > Does the Perl 5 example above convince you that it is the same problem? > Or at least a subset, given that I didn't follow the "assignment > vs. binding" thing? We're now talking about 3 different things: dynamic binding, dynamic scoping, and temporization. Dynamic binding is any association of a value with an identifier, when that association is made at runtime. It's much broader than what we're talking about here. So, let's stop using that term. Now I'm confused again. Isn't that exactly what we're talking about, but generalized to include binding of non-variables? On dynamic scoping and temporization, I agree that they're related problems, in that some definitions of temporization use dynamic scoping. But either feature can be used without the other, so it's better to separate the implementation of dynamic scoping from the implementation of temporization. OK. > Also, the particular implementation suggested -- the Location class > hierarchy of abstract and instantiable classes, the stack of stored > values -- is more heavy weight than the underlying concept merits. > > If you can think of a lighter-weight approach, I'm all ears. I proposed > something fairly lightweight nearly a year ago, with nearly the same > functionality, though it only addressed globals. Leo quite reasonably > objected that it didn't fully implement Perl 5 "local", and I agreed > that it was too particular to globals to be generalized, so I withdrew > it. The new approach attempts to address that limitation by introducing > PMC classes to support structure and variable binding distinctly in an > extensible way. Now it appears that I need to be both (a) more > complete, to handle assignment as opposed to just binding, and (b) > lighter in weight. Please pick just one. ;-} Lighter weight, by separating out the problems into separate components. Assignment and binding are relevant to temporization, not to dynamic scoping. ??? Do you mean "The assignment versus binding distinction is relevant to temporization, not ..."? If so, I'm perfectly happy to ignore it. > I would like to see this implemented, so I'm willing to go back to the > drawing board for a third go. Before I do, though, I would just like to > be reasonably sure that I'm aimed in the right direction. Step one: Write a proposal for implementing dynamic scope and remove all references to temporization. OK. I assume "dynamic scope" can include Perl 5 "local" as applied to variables; does it exclude "local" applied to structure locations? Bonus: Try one data structure per scope, that tracks the dynamically scoped items in that scope. This data structure can be captured by a continuation. Don't create the data structure unless there are dynamically scoped items declared in the scope. Try making the interface as simple as the lexical C<.lex>. What is the purpose of this "one data structure per scope"? I certainly understand why it works to have one data structure per sub for C<.lex>, despite the fact that each variable may be introduced in a different block. But dynamically-scoped variables need more fine-grained handling. For concreteness, here is another Perl 5 example: sub foo { # [span 1] { local $bar = compute_bar(); # [span 2] { local $baz = compute_baz(); # [span 3] } # [span 4] } # [span 5] } To belabor the obvious, the extents of the two "local" bindings divide the sub into five spans, with different dynamic environments going from one to the next. These dynamic environments must be established and disestablished individually, so that "compute_bar()" does not see a new "$baz" binding in scope, but "compute_baz()" does see the new "$bar". The current proposal deals with this by using opcodes that add and subtract from the dynamic environment. I think you are saying that you would prefer that I do this declaratively using one data structure per sub (or per block?). To me, this seems harder, especially since values from "compute_bar()" and "compute_baz()" must be supplied somehow. Am I missing something? . . . > Here is a CL version of the Perl > program above; it is almost identical, modulo syntax changes. > > ;;; Illustration of Common Lisp "special" bindings. This is an > ;;; "uncompiled" version of the "binding at two call levels" > ;;; test case from t/op/globals.t in the patch. The order of > ;;; the three subs is immaterial. -- rgr, 7-Dec-06. > > (defpackage foobar) > > (in-package :foobar) > > (defvar foo) > > (defun main () > (setq foo "Outer value") > (show-value) > (test1) > (show-value)) > > (defun test1 () > (let ((foo "Inner value")) > (show-value))) > > (defun show-value () > (format t "~A~%" foo)) > > (main) The important point here is that C<defvar> is a declaration to create a dynamically scoped variable (or "special variable" as they call it). In the Perl example, it's the C<local> that makes 'foo' behave dynamically scoped, and it will return to being an ordinary global as soon as that C<local> declaration goes out of scope. In both cases, dynamic scoping is a property of the variable, in much the same way that lexical scoping is a property of a variable. I would cavil that dynamic scoping is a property of the binding; I can rewrite this example without the C<defvar>. It is a minor point, but I mention it in the hope of foiling future misunderstandings. > Besides being an important feature in its own right (CL I/O depends > heavily upon dynamic binding), it is traditionally used to implement > nonlexical features such as CATCH and THROW, which typically > communicate via a dynamically-bound alist of CATCH tags. Not having > dynamic binding has effectively stalled my compiler project for about > six months now. Since we've apparently been using "dynamic binding" to mean two completely different things, I'll try to translate. CATCH and THROW in CL rely on a dynamically scoped list variable. And, extrapolating: at each dynamic scope is (potentially) a list of catch tags. When throw is looking for the catch tags that match the throw tag, it first searches the current top-most list of catch tags in the dynamically scoped variable. If it fails to find a match in the top-most list, does it traverse back through the stack of dynamic values for the list? Not necessary; at any given time, the list contains all valid CATCH tags. The translation process does something like this (omitting details of how results from the continuation are handled): (catch <tag> <body>) => (let ((*catch-bindings* (cons (cons <tag> (%make-continuation)) *catch-bindings*))) (declare (special *catch-bindings*)) <body>) So each shadowed binding of *catch-bindings* contains a tail of all of the newer bindings. That is, is there a reason you need to implement exception handling using custom data structures rather than relying on Parrot's exceptions? I am not suggesting that Parrot's exceptions be changed or replaced. I was only pointing out a different mechanism for keeping track of the handlers that are currently in scope, using a dynamically-scoped variable and a Pair (or some kind of array, but using a Pair would be more efficient). > 2. I have an implementation strategy for bringing C<throw> up to > PDD23 that relies on dynamic binding . . . Sounds like a topic for a separate proposal. Not really enough here to evaluate. Yes. By all means, let's stick to one proposal at a time. > 4. As currently implemented, the Coroutine class requires all > C<yield> instructions to be in the initial coroutine sub (indeed, this > IS the coroutine), which is limiting. To fix this, Coroutine should be > redone as a variant of a Continuation, which probably requires minor API > changes to the way in which coroutines are created. However, the > C<yield> interface can remain the same as long as there is a "current > coroutine" concept which is dynamically scoped -- an ideal application > for a dynamic variable binding (again, thread-local). What do you mean by allowing a C<yield> instruction outside the coroutine sub? That sounds about like a C<return> instruction outside of a subroutine/compilation unit. I'm not convinced this is a problem that needs to be fixed, but if you have more thoughts on it you might put them in another separate proposal. Allison The problem was described in the "Coroutines in Lua" thread [1], but I'll summarize. The gist is that the current design of Parrot can't support the Lua coroutine API. In fact, I was not able to implement a solution to the "same fringe problem" using the Coroutine PMC. Since (AFAIK) this is the smallest problem for which the coroutine solution is both efficient and significantly simpler than any known alternatives, that argues strongly that the Parrot PMC implementation is broken. But I'll save the implementation details for a proper proposal. -- Bob [1] http://groups.google.com/group/perl.perl6.internals/browse_frm/thread/b5d7279ce4a0729e/fd8d67b5569fbce2?#fd8d67b5569fbce2