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.

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.

[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."]


   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;


   > 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.

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.

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 [1], 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 [2].  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.

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.

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>.

Oh, I have lots of ideas:

   1.  First and foremost, I need for my compiler to support this basic
CL feature.  (Right now, it kludges around this with C<pushaction>, but
the implementation is really gross.)

Agreed that using C<pushaction> for this is a kludge. We can do better.

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.


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 [3], 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? That is, is there a reason you need to implement exception handling using custom data structures rather than relying on Parrot's exceptions?

   2.  I have an implementation strategy for bringing C<throw> up to
PDD23 that relies on dynamic binding -- and here the per-thread aspect
becomes important.  How does one keep track of bound handlers as they
are tried in such a way that they are not in scope when invoked, yet are
still restored if and when a continuation exits?  The following Perl 5
pseudocode should give the flavor of it:

        sub throw {
            my $exception = shift;

            local @BOUND_HANDLERS = @BOUND_HANDLERS;
            while (@BOUND_HANDLERS) {
                pop(@BOUND_HANDLERS)->($exception);
            }
            # still unhandled . . .
        }

This cannot be implemented in C as written since invoking the handler
via an inferior runloop would make it impossible for the handler to call
an exit continuation (or BE an exit continuation).  Fortunately, the
rebinding of @BOUND_HANDLERS makes it easy to rewrite this as a tail
recursion, given suitable C-calling magic in the continuation.  It also
serves the purpose of keeping each handler out of its own scope, while
restoring the handler state appropriately if a handler exits.

Sounds like a topic for a separate proposal. Not really enough here to evaluate.

   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

Reply via email to