> Marc Feeley wrote: > >> > >>This is unfortunate, since this SRFI really moves all that "legacy" > >>code into non-specifiedness... > > > > > > Since SRFI 39 did not exist when the legacy code was written the code > > never conformed to it. So SRFI 39 does not "move" the code into > > non-specifiedness. The other choice (of having "make-parameter" > > create the mutable parameters as specified in SRFI 39) is even less > > appealing because in that case PLT legacy code would no longer work if > > PLT adopted that semantics. > > That is exactly the reason why I think having fully thread-local > parameters is the more practical solution. The "legacy" code I talk of > is the (perhaps not unconsiderable) amount of code written that uses > parameters that do not share their value among different threads > (as in PLT). But a SRFI is (almost) always a compromise between compatibility with existing systems and proposing the "right" semantics. This SRFI is no different. The best way to be compatible with PLT is to take the specification out of the PLT manual. Of course that is probably not going to be compatible with other systems... What I am proposing is compatible with existing systems (that implement parameters) and it also specifies what I think is the "right" semantics for mutable parameters in the presence of threads. > >>I think parameters (as used in Chez, PLT or (say) Chicken) are > >>(IMHO) well understood and heavily used. > > > > > > For these systems and Gambit-C, "make-mutable-parameter" and > > "make-parameter" can be defined as the native "make-parameter" when > > multithreading is not used. The semantic differences only matter with > > PLT when threads are being used and the parameters are mutated (indeed > > only PLT and Chicken implement threads and the Chicken manual does not > > explain the interaction between parameters and threads). > > [It does, albeit not explicitly enough (see the SRFI-18 section). > I will fix this.] > > > I view SRFI > > 39's main contribution as a specification of how mutation of > > parameters should work in the presence of threads. > > Absolutely. But I think one should be more pragmatic here and > use parameters as user-defined *fully* thread-local storage, > dynamically scoped. It is the "fully" that I don't like. With the semantics I propose a user can decide what is shared and what is not shared (i.e. fully thread-local). For example, if I want to create a thread that has a fully thread-local binding for the parameter current-input-port then you can simply say: (define (make-thread-with-local-input-port thunk) (let ((cip (current-input-port))) (make-thread (lambda () (parameterize ((current-input-port cip)) (thunk)))))) (make-thread-with-local-input-port (lambda () ...)) Or if you want to be more general: (define (make-thread-with-local params thunk) (let ((cp (map (lambda (p) (p)) params))) (make-thread (lambda () (define (bind params cp thunk) (if (null? params) (thunk) (parameterize (((car params) (car cp))) (bind (cdr params) (cdr cp) thunk)))) (bind params cp thunk))))) (make-thread-with-local (list current-input-port current-output-port) (lambda () ...)) This way the user can choose what is shared and not on a per thread basis. > > No "copy-on-write" is not a valid implementation. The reason is that > > the "swapping" semantics requires the child thread to have an > > independent copy of the parent's thread. So the child must get a > > snapshot of the parent's dynamic environment which will make the > > child's mutations invisible to the parent ***AND*** the parent's > > mutations invisible to the child. The copy-on-write approach you > > suggest only makes the child's mutations invisible to the parent. > > That's not true. I'm probably expressing myself not clearly enough. > `make-parameter' creates a parameter in a global environment (not attached) > to any thread. Thread-creation copies the parent's parameter-env, > *but* this env will be empty if the parent has not yet changed it's > parameter-env (i.e. it is equivalent to the global env). But can the global env be mutated by the main thread? In other words if the main thread does (current-output-port stderr) and (parameterize ((current-output-port stderr)) ...) does it mutate the global env? My understanding was that it did (thus thread creation would have to copy the global env to get a consistent "snapshot" of the dynamic environment). If the global env is immutable, then parameter-env could be as big (or bigger depending on your implementation) as the global env. So scalability is an issue. The "swapping semantics" hides a (potentially expensive) copying operation from the user. I prefer when expensive operations are explicit so the user has some control over them. > I'm pretty sure that it works. I would be delighted to see some sample > code that demonstrates the problem. > > > > > When I say "clean" I don't mean it so subjectively. A strong argument > > can be made that the semantics I propose in SRFI 39 for dynamic > > binding are closer to the lexical binding semantics. What does this > > mean? Well, if you look at how environments are manipulated in the > > denotational semantics (section 7.2 of R5RS) you will see that there > > are only two operations on environments "lookup" and "extends". > > "lookup" returns the location in the store that is bound to an > > identifier. The value associated to a location in the store is > > obtained with "hold" and "assign" changes the value associated with > > the location. Dynamic binding as I propose it uses exactly the same > > operations (with the minor point that the domain of "lookup" and > > "extends" must be changed to accept parameter objects). In addition > > to the lexical environment, the semantic functions also need an extra > > environment: the dynamic environment. The dynamic binding semantics > > is simply obtained by having procedure call pass this dynamic > > environment to the called procedure (and this is the only thing that > > distinguished it from lexical binding). Similarly (but obviously > > outside the scope of R5RS), the creation of a new thread would capture > > the dynamic environment of the parent thread. That's it. There is no > > need for an additional semantic operation for copying dynamic > > environments (which is non trivial because it must allocate store). > > Dynamic binding is a simple and natural extension of the R5RS > > Perfectly valid, but to repeat: I see parameters as a simple and effective device > for having thread-local storage, nothing more. My proposal does not prevent thread-local storage. See my previous comment. > I find the reasons you give insufficient for changing a well-established > construct like parameters. The performance argument is weak, incompatibility > to existing systems will make adoption of this SRFI harder and compatibility > to non-released software is unimportant. Additionally it puts unneccessary burden > on implementors and confuses users of implementations that have parameters and threads. I don't believe it would confuse users... there are few users that combine threads and parameters and mutation, and those that do can easily understand the semantics I propose (or indeed they are perhaps incorrectly assuming the semantics I propose which is simpler and more in line with the R5RS semantics!). I am not changing the semantics of a well-established construct... if I was proposing that "make-parameter" add a handler for a command-line option then I would see your point... > (BTW, there is a simple way out of this dilemma: use different > names - `make-dynamic' and `let-dynamic' come to mind, for example...) This is why I added "make-mutable-parameter". Once again: make-parameter is compatible with existing systems that support parameters, even those supporting threads. Alternatively, someone (perhaps you?) could propose a SRFI giving a different semantics for "make-parameter" and mutation in the presence of threads. I think my proposal is the "right" way, but I don't have a monopoly on "rightfulness"! Marc