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