Hello!
I am interested in the design choices surrounding SRFI-39: the make-parameter function and parameterize special form. There are specifically 2 issues that I don't understand.
1) The parameter is currently a function that yields the value its holding when it is called. What is the purpose of forcing the user to "dereference" the parameter in this way? This is constrast to implementing dynamic variables using a mutable global variable, and having parameterize directly mutate the global variable within a dynamic-wind context.
2) Is there a way to get a closure to close over the dynamic environment? The use case is that I want to use dynamic variables *purely* to avoid having to pass commonly used arguments explicitly. Thus I am using the dynamic variable as an "implicitly" passed argument. And in such a case, I would like closures to refer to the value of the dynamic variable at the time of closure creation.
Thank you very much! -Patrick
Afficher les réponses par date
On Nov 3, 2013, at 12:27 AM, Patrick Li patrickli.2001@gmail.com wrote:
Hello!
I am interested in the design choices surrounding SRFI-39: the make-parameter function and parameterize special form. There are specifically 2 issues that I don't understand.
- The parameter is currently a function that yields the value its
holding when it is called. What is the purpose of forcing the user to "dereference" the parameter in this way? This is constrast to implementing dynamic variables using a mutable global variable, and having parameterize directly mutate the global variable within a dynamic-wind context.
This implementation technique has problems:
- the cost of call/cc will be proportional to the number of parameters (because at the time of creating a continuation you have to take a snapshot of all those parameters, and when the continuation is invoked you have to copy the snapshot back to the parameters)
- it doesn't handle mutation (you would have to introduce cells for that, and dereference these cells when accessing the parameter)
- it doesn't work in a multithreaded environment (the content of the parameters will be clobbered by the different threads operating on those parameters)
- Is there a way to get a closure to close over the dynamic
environment? The use case is that I want to use dynamic variables *purely* to avoid having to pass commonly used arguments explicitly. Thus I am using the dynamic variable as an "implicitly" passed argument. And in such a case, I would like closures to refer to the value of the dynamic variable at the time of closure creation.
Closures capture the lexical variables. Parameter objects can't be automatically captured by closures because that would mean that a calling function can't use parameter objects to pass implicit parameters to the called function. So if you want to capture the value of some parameter objects you will have to do it manually, i.e. something like:
(define p (make-parameter 42))
(define (make-adder) ;; creates a function which adds the value of parameter p (let ((captured-p (p))) (lambda (x) (parameterize ((p captured-p)) (+ x (p))))))
(define a (make-adder))
(p 1000) ;; change value of parameter p
(pp (a 10)) => 52
(p) => 1000
Marc
Thank you very much for your reply Marc!
This implementation technique has problems:
- the cost of call/cc will be proportional to the number of parameters
(because at the time of creating a continuation you have to take a snapshot of all those parameters, and when the continuation is invoked you have to copy the snapshot back to the parameters)
The cost of call/cc would be proportional to the number of parameterize (and hence dynamic-wind) calls you would be currently nested in right? Other parts of the program, that are not as deeply nested in parameterize, would not be affected. Is my understanding correct?
- it doesn't handle mutation (you would have to introduce cells for
that, and dereference these cells when accessing the parameter)
I don't quite understand this aspect.
I expect: (parameterize [(a 3)] ... body ...) to be roughly equivalent to: (let [(old-a a)] (dynamic-wind (lambda () (set! a 3)) (lambda () ... body ...) (lambda () (set! a old-a)))) So I would expect to be able to use (set! a 10) from within the body just like normal.
- it doesn't work in a multithreaded environment (the content of the
parameters will be clobbered by the different threads operating on those parameters)
Yes. Threading & Dynamic bindings together seem subtle.
- Is there a way to get a closure to close over the dynamic
environment? The use case is that I want to use dynamic variables *purely* to avoid having to pass commonly used arguments explicitly. Thus I am using the dynamic variable as an "implicitly" passed argument. And in such a case, I would like closures to refer to the value of the dynamic variable at the time of closure creation.
Closures capture the lexical variables. Parameter objects can't be automatically captured by closures because that would mean that a calling function can't use parameter objects to pass implicit parameters to the called function. So if you want to capture the value of some parameter objects you will have to do it manually, i.e. something like:
(define p (make-parameter 42))
(define (make-adder) ;; creates a function which adds the value of parameter p (let ((captured-p (p))) (lambda (x) (parameterize ((p captured-p)) (+ x (p))))))
(define a (make-adder))
(p 1000) ;; change value of parameter p
(pp (a 10)) => 52
(p) => 1000
I understand the example that you gave on how to manually capture the parameter object. Could you clarify what you mean by "a calling function can't use parameter objects to pass implicit parameters to the called function"?
Thanks very much for your help. -Patrick
On Sun, Nov 3, 2013 at 6:03 AM, Marc Feeley feeley@iro.umontreal.ca wrote:
On Nov 3, 2013, at 12:27 AM, Patrick Li patrickli.2001@gmail.com wrote:
Hello!
I am interested in the design choices surrounding SRFI-39: the make-parameter function and parameterize special form. There are specifically 2 issues that I don't understand.
- The parameter is currently a function that yields the value its
holding when it is called. What is the purpose of forcing the user to "dereference" the parameter in this way? This is constrast to implementing dynamic variables using a mutable global variable, and having parameterize directly mutate the global variable within a dynamic-wind context.
This implementation technique has problems:
- the cost of call/cc will be proportional to the number of parameters
(because at the time of creating a continuation you have to take a snapshot of all those parameters, and when the continuation is invoked you have to copy the snapshot back to the parameters)
- it doesn't handle mutation (you would have to introduce cells for that,
and dereference these cells when accessing the parameter)
- it doesn't work in a multithreaded environment (the content of the
parameters will be clobbered by the different threads operating on those parameters)
- Is there a way to get a closure to close over the dynamic
environment? The use case is that I want to use dynamic variables *purely* to avoid having to pass commonly used arguments explicitly. Thus I am using the dynamic variable as an "implicitly" passed argument. And in such a case, I would like closures to refer to the value of the dynamic variable at the time of closure creation.
Closures capture the lexical variables. Parameter objects can't be automatically captured by closures because that would mean that a calling function can't use parameter objects to pass implicit parameters to the called function. So if you want to capture the value of some parameter objects you will have to do it manually, i.e. something like:
(define p (make-parameter 42))
(define (make-adder) ;; creates a function which adds the value of parameter p (let ((captured-p (p))) (lambda (x) (parameterize ((p captured-p)) (+ x (p))))))
(define a (make-adder))
(p 1000) ;; change value of parameter p
(pp (a 10)) => 52
(p) => 1000
Marc
On Nov 3, 2013, at 2:18 PM, Patrick Li patrickli.2001@gmail.com wrote:
Thank you very much for your reply Marc!
This implementation technique has problems:
- the cost of call/cc will be proportional to the number of parameters
(because at the time of creating a continuation you have to take a snapshot of all those parameters, and when the continuation is invoked you have to copy the snapshot back to the parameters)
The cost of call/cc would be proportional to the number of parameterize (and hence dynamic-wind) calls you would be currently nested in right? Other parts of the program, that are not as deeply nested in parameterize, would not be affected. Is my understanding correct?
When a continuation is invoked, some dynamic bindings must first be undone (unwinding phase) and then some dynamic bindings myst be redone (winding phase). So it is proportional to the sum of the nesting depths (or more precisely, the nesting depth from the closest common branches of the dynamic environment).
- it doesn't handle mutation (you would have to introduce cells for
that, and dereference these cells when accessing the parameter)
I don't quite understand this aspect.
I expect: (parameterize [(a 3)] ... body ...) to be roughly equivalent to: (let [(old-a a)] (dynamic-wind (lambda () (set! a 3)) (lambda () ... body ...) (lambda () (set! a old-a)))) So I would expect to be able to use (set! a 10) from within the body just like normal.
This would work in a single threaded Scheme implementation, but not in a multithreaded implementation. For your information this is called the "shallow binding" implementation, and it was (is) used in some implementations of Lisp (google it).
- it doesn't work in a multithreaded environment (the content of the
parameters will be clobbered by the different threads operating on those parameters)
Yes. Threading & Dynamic bindings together seem subtle.
- Is there a way to get a closure to close over the dynamic
environment? The use case is that I want to use dynamic variables *purely* to avoid having to pass commonly used arguments explicitly. Thus I am using the dynamic variable as an "implicitly" passed argument. And in such a case, I would like closures to refer to the value of the dynamic variable at the time of closure creation.
Closures capture the lexical variables. Parameter objects can't be automatically captured by closures because that would mean that a calling function can't use parameter objects to pass implicit parameters to the called function. So if you want to capture the value of some parameter objects you will have to do it manually, i.e. something like:
(define p (make-parameter 42))
(define (make-adder) ;; creates a function which adds the value of parameter p (let ((captured-p (p))) (lambda (x) (parameterize ((p captured-p)) (+ x (p))))))
(define a (make-adder))
(p 1000) ;; change value of parameter p
(pp (a 10)) => 52
(p) => 1000
I understand the example that you gave on how to manually capture the parameter object. Could you clarify what you mean by "a calling function can't use parameter objects to pass implicit parameters to the called function"?
Lets assume that parameter objects are automatically captured by closures. That means that when a closure is called, the dynamic bindings that were in effect at the moment that the closure was created will be reinstated at the beginning of the execution of the body of the closure. Now examine the following code and ponder what is returned by the last 3 expressions:
(define p (make-parameter 111))
(define (make-adder x) (parameterize ((p x)) (f)))
(define (f) (lambda (y) (+ y (p))))
(define g (make-adder 222)) (define h (f))
(g 0) => ? (h 0) => ? (parameterize ((p 333)) (h 0)) => ?
The closure in g remembers that p was bound to 222, and the closure in h remembers that p was bound to 111. So in the call (g 0) the result will be 222 (i.e. 0+222), and in the first call (h 0) the result will be 111 (i.e. 0+111). What happens in the last expression? The parameter p is dynamically bound to 333, and then h is called. Because h remembers that p was bound to 111, it reinstates this binding for the evaluation of its body, (+ y (p)), so the result is 111 (i.e. 0+111). So that's what I meant when I said "a calling function can't use parameter objects to pass implicit parameters to the called function". The idiom (parameterize ((p ...)) (fn ...)) no longer works for passing the implicit parameter p to fn.
Note that when parameter objects are automatically captured by closures, parameter objects behave like lexical variables! Consequently they would be completely redundant in the language.
Marc
The closure in g remembers that p was bound to 222, and the closure in h remembers that p was bound to 111. So in the call (g 0) the result will be 222 (i.e. 0+222), and in the first call (h 0) the result will be 111 (i.e. 0+111). What happens in the last expression? The parameter p is dynamically bound to 333, and then h is called. Because h remembers that p was bound to 111, it reinstates this binding for the evaluation of its body, (+ y (p)), so the result is 111 (i.e. 0+111). So that's what I meant when I said "a calling function can't use parameter objects to pass implicit parameters to the called function". The idiom (parameterize ((p ...)) (fn ...)) no longer works for passing the implicit parameter p to fn.
Note that when parameter objects are automatically captured by closures, parameter objects behave like lexical variables! Consequently they would be completely redundant in the language.
I understand now. Thank you for the in-depth explanation. Okay, I realize that I am asking for something slightly inconsistent. Do you have any ideas then how I could design a library interface to accomplish the following?
EXPLICIT PARAMETER PASSING:
Consider a library for managing stacks. (make-stack) : Create a new stack (push stack item) : Pushes the item onto the stack. (peek stack) : Retrieves the top of the stack. (pop stack) : Pops off the top of the stack. (empty? stack) : Checks if the stack is empty.
Usage of this library would be standard: (define (make-random-stack) (let [(mystack (make-stack))] (push mystack 0) (push mystack 1) (store-random-callback (lambda () (push mystack 0))) (helper mystack) mystack))
(define (helper s) (pop s))
IMPLICIT PARAMETER PASSING:
It is annoying to have to thread the stack variable through the argument lists of all my functions. So I would rather have it passed implicitly. The (with-stack a-stack ...) command/macro tells the stack library that I would like all following push/pop/peek commands to operate on the given a-stack.
(define (make-random-stack) (let [(mystack (make-stack))] (with-stack mystack (push 0) (push 1) (store-random-callback (lambda () (push 0))) (helper)) mystack))
(define (helper) (pop))
I feel that this is something reasonable to ask for, even though I now know that there is some inherent ambiguity (related to when and when not to capture) in the interface.
Thank you again for the in-depth explanations. -Patrick
On Mon, Nov 4, 2013 at 7:46 AM, Marc Feeley feeley@iro.umontreal.ca wrote:
On Nov 3, 2013, at 2:18 PM, Patrick Li patrickli.2001@gmail.com wrote:
Thank you very much for your reply Marc!
This implementation technique has problems:
- the cost of call/cc will be proportional to the number of parameters
(because at the time of creating a continuation you have to take a snapshot of all those parameters, and when the continuation is invoked you have to copy the snapshot back to the parameters)
The cost of call/cc would be proportional to the number of parameterize (and hence dynamic-wind) calls you would be currently nested in right? Other parts of the program, that are not as deeply nested in parameterize, would not be affected. Is my understanding
correct?
When a continuation is invoked, some dynamic bindings must first be undone (unwinding phase) and then some dynamic bindings myst be redone (winding phase). So it is proportional to the sum of the nesting depths (or more precisely, the nesting depth from the closest common branches of the dynamic environment).
- it doesn't handle mutation (you would have to introduce cells for
that, and dereference these cells when accessing the parameter)
I don't quite understand this aspect.
I expect: (parameterize [(a 3)] ... body ...) to be roughly equivalent to: (let [(old-a a)] (dynamic-wind (lambda () (set! a 3)) (lambda () ... body ...) (lambda () (set! a old-a)))) So I would expect to be able to use (set! a 10) from within the body just like normal.
This would work in a single threaded Scheme implementation, but not in a multithreaded implementation. For your information this is called the "shallow binding" implementation, and it was (is) used in some implementations of Lisp (google it).
- it doesn't work in a multithreaded environment (the content of the
parameters will be clobbered by the different threads operating on those parameters)
Yes. Threading & Dynamic bindings together seem subtle.
- Is there a way to get a closure to close over the dynamic
environment? The use case is that I want to use dynamic variables *purely* to avoid having to pass commonly used arguments explicitly. Thus I am using the dynamic variable as an "implicitly" passed argument. And in such a case, I would like closures to refer to the value of the dynamic variable at the time of closure creation.
Closures capture the lexical variables. Parameter objects can't be automatically captured by closures because that would mean that a calling function can't use parameter objects to pass implicit parameters to the called function. So if you want to capture the value of some parameter objects you will have to do it manually, i.e. something like:
(define p (make-parameter 42))
(define (make-adder) ;; creates a function which adds the value of parameter p (let ((captured-p (p))) (lambda (x) (parameterize ((p captured-p)) (+ x (p))))))
(define a (make-adder))
(p 1000) ;; change value of parameter p
(pp (a 10)) => 52
(p) => 1000
I understand the example that you gave on how to manually capture the parameter object. Could you clarify what you mean by "a calling function can't use parameter objects to pass implicit parameters to the called function"?
Lets assume that parameter objects are automatically captured by closures. That means that when a closure is called, the dynamic bindings that were in effect at the moment that the closure was created will be reinstated at the beginning of the execution of the body of the closure. Now examine the following code and ponder what is returned by the last 3 expressions:
(define p (make-parameter 111))
(define (make-adder x) (parameterize ((p x)) (f)))
(define (f) (lambda (y) (+ y (p))))
(define g (make-adder 222)) (define h (f))
(g 0) => ? (h 0) => ? (parameterize ((p 333)) (h 0)) => ?
The closure in g remembers that p was bound to 222, and the closure in h remembers that p was bound to 111. So in the call (g 0) the result will be 222 (i.e. 0+222), and in the first call (h 0) the result will be 111 (i.e. 0+111). What happens in the last expression? The parameter p is dynamically bound to 333, and then h is called. Because h remembers that p was bound to 111, it reinstates this binding for the evaluation of its body, (+ y (p)), so the result is 111 (i.e. 0+111). So that's what I meant when I said "a calling function can't use parameter objects to pass implicit parameters to the called function". The idiom (parameterize ((p ...)) (fn ...)) no longer works for passing the implicit parameter p to fn.
Note that when parameter objects are automatically captured by closures, parameter objects behave like lexical variables! Consequently they would be completely redundant in the language.
Marc
On Nov 4, 2013, at 12:41 PM, Patrick Li patrickli.2001@gmail.com wrote:
The closure in g remembers that p was bound to 222, and the closure in h remembers that p was bound to 111. So in the call (g 0) the result will be 222 (i.e. 0+222), and in the first call (h 0) the result will be 111 (i.e. 0+111). What happens in the last expression? The parameter p is dynamically bound to 333, and then h is called. Because h remembers that p was bound to 111, it reinstates this binding for the evaluation of its body, (+ y (p)), so the result is 111 (i.e. 0+111). So that's what I meant when I said "a calling function can't use parameter objects to pass implicit parameters to the called function". The idiom (parameterize ((p ...)) (fn ...)) no longer works for passing the implicit parameter p to fn.
Note that when parameter objects are automatically captured by closures, parameter objects behave like lexical variables! Consequently they would be completely redundant in the language.
I understand now. Thank you for the in-depth explanation. Okay, I realize that I am asking for something slightly inconsistent. Do you have any ideas then how I could design a library interface to accomplish the following?
EXPLICIT PARAMETER PASSING:
Consider a library for managing stacks. (make-stack) : Create a new stack (push stack item) : Pushes the item onto the stack. (peek stack) : Retrieves the top of the stack. (pop stack) : Pops off the top of the stack. (empty? stack) : Checks if the stack is empty.
Usage of this library would be standard: (define (make-random-stack) (let [(mystack (make-stack))] (push mystack 0) (push mystack 1) (store-random-callback (lambda () (push mystack 0))) (helper mystack) mystack))
(define (helper s) (pop s))
IMPLICIT PARAMETER PASSING:
It is annoying to have to thread the stack variable through the argument lists of all my functions. So I would rather have it passed implicitly. The (with-stack a-stack ...) command/macro tells the stack library that I would like all following push/pop/peek commands to operate on the given a-stack.
(define (make-random-stack) (let [(mystack (make-stack))] (with-stack mystack (push 0) (push 1) (store-random-callback (lambda () (push 0))) (helper)) mystack))
(define (helper) (pop))
I feel that this is something reasonable to ask for, even though I now know that there is some inherent ambiguity (related to when and when not to capture) in the interface.
Thank you again for the in-depth explanations. -Patrick
It seems what you really want is an implicit parameter for specific functions. This could be achieved by the with-stack macro which would add the stack as the first parameter of calls to push, pop, etc in the body. In other words:
(with-stack <stack> <body>)
would expand into something like
(let ((the-stack <stack>)) (let ((push (lambda (item) (push the-stack item))) (peek (lambda () (peek the-stack))) (pop (lambda () (pop the-stack)))) <body>))
Note the use of a plain inner "let" to create the specialized stack operations (a "letrec" would not work).
Marc