Am Mi., 8. Sept. 2021 um 10:19 Uhr schrieb Shiro Kawai <xxxxxx@gmail.com>:
Regarding the semantics of parameter mutation.  The draft says:

Moreover, this specification makes a decision on the behavior of mutable parameter objects with respect to multiple threads. If a parameter that is inherited by a newly created thread and has not been reparameterized is mutated by one of the threads, the mutation will also (eventually) be observed by the other thread. This is a clear semantics because this is the same behavior as implemented by lexical variables.

Although I agree that the proposed semantics can be considered parallel to the lexical variable semantics,

It's also important for parallelity with the promise semantics.  Replacing them by, say, thread-driven futures shouldn't change their semantics.
 
I argue against it: Shared mutable parameters are of little use.

It is unreliable to use it for inter-thread communication, for the parameter can be reparameterized without your knowledge, because of its "dynamic scoping" nature.   That means you can't locally determine the effect of mutating a parameter (whether it's thread-local or shared), so you have to always reparametize when you need to mutate a parameter with a consistent behavior.

The same arguments could be given against set!-ing (global) variables.  Or, to say it differently, if you do mutation (whether of global variables or parameters) you have to be aware of what you are doing and what the implications are.

Threads may add some complexity, but the known issues with mutating global variables (or parameters) are already present without them.
 
Besides, there's no guarantee on atomicity of mutating parameters.  It's always better to use dedicated synchronization primitives for inter-thread communication.

Same as for mutable global variables, see above.

That said, I consider adding a paragraph that SRFI 226, at least when implemented for R6RS with its safety guarantees, defines safe libraries, meaning that, in particular, setters and getters of Scheme values have to be atomic.
 
On the other hand, thread-local parameter mutation comes handy.  For example,  suppose you want a library that collects per-thread event logging.

(define log-sink (make-parameter #f))   ;; (<thread> . <sink>)

(define (get-sink)
  (let ((p (log-sink)))
     (if (and p (eq? (car p) (current-thread)))
       (cdr p)
       (let ((sink (make-sink)))
          (log-sink (cons (current-thread) sink))
          sink))))

(deifne (log-push! item)
  (write-log-to-sink item (get-sink)))

We want log-push! to work just by importing to the code that uses it.   We don't want to (or even can't) go to where the thread is created and wrap the thread thunk with (parameterize ((log-sink #f)) ...).

What you want to achieve seems outside the scope of SRFI 226's parameters alone.  Note that parameters can be captured in one thread and reinstated in another one, so an implicit thread-local semantics will get quite complicated, I think.

You would have to make thread-local cells explicit.  I understand that SRFI 226's threading library doesn't include these, yet, but they should probably be added.

You would then write:

(define log-sink (make-tl-box #f)) ;; <sink>

(define (get-sink)
  (cond
   ((tl-box-ref log-sink))
   (else
     (let ((sink (make-sink)))
       (tl-box-set! log-sink sink)
       sink))))

It's even clearer than the above code, I think, and is a hint on why thread-local parameters are the optimal solution to the problem. 
 
What do you think of such an interface (similar to C11's or Posix's interface to dynamic thread-local storage)?

Gauche has been using this thread-local parameters, so I don't know if there's a useful case for shared-mutable-state parameters.  I'd like to know if there are some.

Anything that involves a shared counter, for example.  Say, a gensym implementation so that parameterize can be used to dynamically locally reset the parameter.

Of course, modifying the counter has to be protected by locks.

Maybe most mutating code will actually ask for thread-local mutation, but then it should be made explicit by the above arguments.  Conceptually, parameters are orthogonal to thread-local storage and thus their low-level interfaces should not be conflated.  (The only necessary connection between the two is that the current parameterization can be implemented as a thread-local variable.)

Marc