I find parameters an unpleasant hack. I can't understand why RnRS doesn't just add dynamic variables back in (as lispm pointed out to me, they were in R0RS).
What do you gain by having fluid variables rather than parameters? As I understand it, they serve the same purpose, and the difference between them is just an implementation detail.
Firstly, parameters are functions. That means that if the function you're using didn't plan for use with parameters (maybe the code predates R7RS, maybe that value was never supposed to be changed), you're out of luck: have fun with your globals.
Secondly, parameters must be global. You can't, say, have a function that depends on which parameters were bound in a parent function. This is because parameters aren't true dynamic scoping: they're using lexical scoping to emulate dynamic scoping. All well and good, but why not just implement dynamic scoping?
Thirdly, Parameters are fiddly to get right. The path to RnRS parameters is paved with the bones of many a construct (like fluid-let) that tried and failed to safely handle parameters, whether it be because of multithreading, or because of those two famous bugbears of Scheme programming, call/cc and dynamic-wind. The reason for this is that all these constructs, at their heart, are changing the value of a global for the dynamic extent of a function. In scheme, as you no doubt know, dynamic extents aren't guaranteed contiguous.
OTOH, true dynamic variables are a lot simpler to get right in the prescence of continuations: a continuation is a copy of the stack, in low level terms. Dynamic variables are bound to the stack, so dynamic variables and continuations should Just Work. The same is true with threading.
In short, parameters are a construct less flexable than true dynamic variables and a more complicated one to implement, to boot.
I disagree that dynamic variables are more complicated to implement, though. As you mentioned, parameters are fiddly to get right. Dynamic variables, on the other hand, only need to be defined in what I'll call a "fluid environment", which is basically the same as a lexical environment, but it gets passed to `apply` whenever a procedure is applied, the same way the lexical environment gets passed to `eval` whenever it's applied. Under the hood, all the usual operations on environments work without change, because the data structure is the same, though you'll need to expose an API to the extra environment for it to be useful.
Anyway, I don't think the implementation of parameters as procedures (even global ones) necessarily prohibits them from working with call/cc. Consider an implementation like this:
(define (make-parameter value)
(case-lambda
(() value)
((x f)
(let ((old value))
(dynamic-wind
(lambda () (set! value x))
f
(lambda () (set! value old)))))))
(define-syntax parameterize
(syntax-rules ()
((parameterize ((param val)) body ...)
(param val (lambda () body ...)))
((parameterize ((param val)
(params vals)
...)
body ...)
(param val (lambda ()
(parameterize ((params vals)
...)
body ...))))))
Yes, it's still a kludge compared to real fluid variables, but I think it's safe in the face of continuations (I only did minimal testing, so I don't guarantee it). This kind of thing is exactly what dynamic-wind is for :)
>implementation of parameters as procedures (even global ones) necessarily prohibits them from working with call/cc.
It appears I didn't write clearly: the fact that they are procedures doesn't make them not work with call/cc. As I understand it, the procedures mechanism actually makes it easier to make work, which is why Feely wrote the SRFI that way, as opposed to earlier mechanisms like fluid-let, which did use variables.
The procedures are really just a way to control access to the variables. If you use normal global variables, people can treat them like lexical variables which breaks the semantics of dynamic scope. With the procedures, updates are forced to go through the dynamic-wind mechanism and forced to "pop" on exit from the dynamic extent. It's just a way to leverage the call stack to implement the semantics, but I agree it's much nicer (and cleaner) to do it the "real way" handing the fluid environment to `apply`.
I think the use of procedures is fine, but not ideal. At the very least, I think the parameters should be abstracted to hide the fact that they're procedures. This is trivial to do now that there's a real record facility, and the extra abstraction would leave the door open for implementors to use an alternative implementation strategy. A carefully-designed API could even make it so that implementors could use procedures+records or the "real way" with clients being none the wiser.
One thing I do like is that parameters are lexically distinct from regular variables, so it's always obvious whether some particular variable is lexical or fluid.
>This is trivial to do now that there's a real record facility
Last I heard, there's still no way for a function to masquerade as a variable. Has this changed?
>One thing I do like is that parameters are lexically distinct from regular variables, so it's always obvious whether some particular variable is lexical or fluid.
See one of my other replies for a usecase of dynamically bound functions (intercepting calls to a function within a certain dynamic extent) that makes me wish otherwise.