[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: let-fluid problem
From: Per Bothner <per@xxxxxxxxxxx>
Date: 15 Nov 1999 23:13:21 -0800
> I would like to argue against any DYNAMIC-WIND + SET! sort of
> "fluid variable" system. The problem is threads. If you have a
> thread model, then any thread switch involves unwinding up the
> stack to the common ancestor continuation, then winding down into
> the activated continuation. This seems unacceptably expensive; thread
> switch should be a low-overhead operation.
(Let us not confuse specification with implementation. When I
was (briefly) involved with C++ standardization, I learned the
"as-if rule." This refers to the fact that a feature may be
defined "as if" it were implemented in a particular way, but
an implementation is free to use a different implementation as long
as long as no well-defined program can tell the difference.)
It's not a question of *confusing* implementation & spec. It's an issue
of the *interaction* between implementation & spec -- can the spec be
provided in important general classes of Scheme implementations?
A more fundamental problem with the fluid-let specification is that it
does not support a thread model that may have true parallel threads.
When the topic of "thread local variables" came up in the Guile
mailing list (see http://sourceware.cygnus.com/ml/guile/1999-03/)
I proposed a "deep binding" model of variable access
Each thread has an association list of fluid bindings.
When fluid-let is evaluated, it conses up a new association to the
front of the list. That becomes the current fluid binding
association list while the body of the fluid-let is evaluated.
The old list is restored when the fluid-let finishes.
(This can be implemented with the appropriate dynamic-wind.)
When a new thread is created, it initializes its fluid
binding list with the *current* list from the parent thread.
Thus all bindings are initially shared. When a non-local
variable is evaluated, the evaluator searches the current thread's
fluid binding list, and finds the first binding for the given name.
If none, is found, the global binding is returned.
When a set! is evaluated, it also searches the fluid bindigs list
of the current thread, and modifies the first association whose
name matches. If there is none, the global binding is modified.
This is of course only the semantic model; an actual implementation
may use caching or other thread-local storage.
Actually, that's a fairly operationally defined "semantics" -- but nonetheless,
you are proposing what Gambit has, and it's pretty close to what I have
proposed (what Scheme 48 has). The important point is handling the interaction
between threads and dynamic binding.
This model that you, Brad, and I are pushing allows threads to share and
not share dynamic scope, as appropriate, just as you said in your msg:
This model is actually very general; it allows rather fine-grained
control over which variables are shared between which threads,
but using a very simple but general mechanism. (As such I think
it first very much in the spirit of Scheme.)
And it's in Kawa, Scheme 48, and Gambit, at least.
The distinction between what Gambit has and what Scheme 48 has is that S48
builds the mechanism on top of a new data type, the "fluid cell." You have to
get your hands on one of these values through the general mechanisms in the
program for passing around data, to reference a dynamically-bound value. This
can be viewed as a sort of lexical or data-flow level of control on this
mechanism. Gambit's dynamic vars allow anyone who can *write down the name* of
a dynamic var to access its dynamically bound value. I would argue that's too
uncontrolled. Also, you have to introduce a whole new linguistic mechanism,
whereas fluid cells just require some new primitive procedures, with no actual
language-level hacking. In practice, this is probably a small point. We are
all reading off the same page.
Lars has made the response that he's not trying to use this SRFI to
come up with a "standard" for dynamic binding; he's just trying to
codify existing practice:
From: Lars Thomas Hansen <lth@xxxxxxxxxxx>
All of these objections are reasonable, but they do not speak to the
purpose of this particular SRFI. The purpose of the SRFI is only to
codify an already existing practice that has slight variations across
implementations (namely, some protect the bindings with DYNAMIC-WIND and
some don't). I submitted it because I find myself using FLUID-LET in
some of my programs, because it is a convenient mechanism for
temporarily and reliably overriding the values of global variables.
This seems like a waste of a SRFI to me. There's not too much to say
about dynamic scoping. The only real issue is their interaction with
threads, and there's basically one answer to how to handle that. The
distinctions between Gambit's, Kawa's and S48's dynamic-scope stuff
are minor. Casting a non-thread-safe mechanism into stone with a SRFI
when one could have gone that one extra step is stopping short.
(More generally, I do not think that the SRFI process is useful only for
designing new stuff; it is useful also for collecting old stuff that is
known to be useful, even in limited contexts.)
In that case, we could take every incompatible R5RS extension of every
Scheme implementation out there and make it a SRFI? Doesn't seem like
a good thing to me. I'd rather use the SRFI process to get people to
come to as wide a consensus as possible on the Right Thing, so that
the SRFI record becomes a growing spec of agreed-upon design. (I bear
in mind that complete agreement is never possible -- just about everyone
(including me) can find something in SRFI-1 they don't like, for example.)
But in the end, it's Lars' SRFI. Perhaps at minimum Lars could add some
text explaining to readers of the SRFI that this mechanism has problems
interacting with threads and that if they do not have backwards-compatibility
issues, they should avoid using it in favor of better dynamic-scope mechanisms
defined in presumed-later SRFIs?