Hi Marc,
I can't recall if this is somewhere in the mailing list archive from before:
I recall that you indicated that parameter objects are expensive.
Would you mind describing/quantifying the average/worst case/best case cost for assigning a value to a parameter object by parameterization (|parameterize|), and for accessing a parameter object (load or store operation by invoking it as a closure)?
Parameter objects are very practical and have ample use scenarios, both high-level and relatively low-level.
Thanks, Adam
Afficher les réponses par date
On Sun, Mar 8, 2020 at 10:44 AM Adam adam.mlmb@gmail.com wrote:
Parameter objects are very practical and have ample use scenarios, both
high-level and relatively low-level.
I would be interested in hearing about these use cases. In particular, parameters (unlike fluid/dynamic variables) are first-class: they can be stored in local variables or data structures. If you have any use cases of this kind, I very much would like to know.
John Cowan http://vrici.lojban.org/~cowan cowan@ccil.org "But I am the real Strider, fortunately," he said, looking down at them with his face softened by a sudden smile. "I am Aragorn son of Arathorn, and if by life or death I can save you, I will."
On Sun, 8 Mar 2020 at 22:57, John Cowan cowan@ccil.org wrote:
On Sun, Mar 8, 2020 at 10:44 AM Adam adam.mlmb@gmail.com wrote:
Parameter objects are very practical and have ample use scenarios, both high-level and relatively low-level.
I would be interested in hearing about these use cases. In particular, parameters (unlike fluid/dynamic variables) are first-class: they can be stored in local variables or data structures. If you have any use cases of this kind, I very much would like to know.
Hi John,
Great to talk to you.
I think Gambit implements "parameters" only, and nothing else that's a similar and stripped down form, if that is what "fluid variables" and "dynamic variables" are.
For completeness can you please provide some reference URL to the latter two or/and describe very briefly?
Thanks, Adam
On Sun, Mar 8, 2020 at 11:17 AM Adam adam.mlmb@gmail.com wrote:
For completeness can you please provide some reference URL to the latter
two or/and describe very briefly?
For fluid variables, see the withdrawn SRFI 15, < https://srfi.schemers.org/srfi-15/srfi-15.html%3E. Dynamic variables are an equivalent Common Lisp concept. Fluid and dynamic variables behave like global variables that have been bound permanently to a parameter, so they are accessible from everywhere and can be parameterized anywhere. In Common Lisp, all top-level variables (but not constants or functions) are dynamic, and conventionally are written with an asterisk as a prefix and suffix. In addition, when a variable in a Common LIsp lambda-list (or equivalent) is dynamic, it will be parameterized rather than lexically bound when the procedure is called.
All the examples I have found (except Marc's, thank you Marc) involve using parameters in exactly this way, which I why I am especially interested in other use cases.
There is a problem with parameters: if a procedure implicitly depends on a parameter, and you want to use the procedure in a "localized" way, then binding the parameter around the whole or a substantial part of a program is convenient. But to use such a procedure in a "universal" way independent of its context, it is necessary to wrap *every* call in a parameterize expression.
John Cowan http://vrici.lojban.org/~cowan cowan@ccil.org Objective consideration of contemporary phenomena compel the conclusion that optimum or inadequate performance in the trend of competitive activities exhibits no tendency to be commensurate with innate capacity, but that a considerable element of the unpredictable must invariably be taken into account. --Ecclesiastes 9:11, Orwell/Brown version
For fluid variables, see the withdrawn SRFI 15.
Were fluid variables ever a part of any Scheme standard?
Were any dynamic-variable-style facilities before R7RS?
How does R6RS deal with dynamic variables - is the user expected to roll their own using dynamic-wind? I though parameterize was in that standard as well, but I looked again and it's not. Just some R6RS implementations have it.
No fluid or dynamic variables in any standard before R7RS, and it is trivial to roll your own parameters provided they don't have to interact with threads: see R7RS.
On Sun, Mar 8, 2020 at 12:48 PM Lassi Kortela lassi@lassi.io wrote:
For fluid variables, see the withdrawn SRFI 15.
Were fluid variables ever a part of any Scheme standard?
Were any dynamic-variable-style facilities before R7RS?
How does R6RS deal with dynamic variables - is the user expected to roll their own using dynamic-wind? I though parameterize was in that standard as well, but I looked again and it's not. Just some R6RS implementations have it.
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://mailman.iro.umontreal.ca/cgi-bin/mailman/listinfo/gambit-list
On Mar 8, 2020, at 12:39 PM, John Cowan cowan@ccil.org wrote:
All the examples I have found (except Marc's, thank you Marc) involve using parameters in exactly this way, which I why I am especially interested in other use cases.
This situation can be generalized to any object whose methods take implicit parameters that depend on the thread and the object.
For example, imagine you have a class C with a method M, and you wish to generate debugging information whenever the current thread calls method M of obj, an instance of C. You only want the debugging information to be generated for that combination of obj and the current thread (possibly because many other threads call method M on other instances of C without any problem).
So each instance of C could have a field with a “debug” parameter object that would be parameterized by any thread needing debugging information to be generated.
If parameter objects could only be bound to global variables you couldn’t have such a fine level of control. First-class citizenship of parameter objects is very useful for scenarios like this. Similarly to procedures and continuations gaining in power when they are first-class.
Marc
Hi John,
1) Delve into dynamic and fluid variables
On Mon, 9 Mar 2020 at 00:39, John Cowan cowan@ccil.org wrote:
On Sun, Mar 8, 2020 at 11:17 AM Adam adam.mlmb@gmail.com wrote:
For completeness can you please provide some reference URL to the latter
two or/and describe very briefly?
For fluid variables, see the withdrawn SRFI 15, < https://srfi.schemers.org/srfi-15/srfi-15.html%3E. Dynamic variables are an equivalent Common Lisp concept. Fluid and dynamic variables behave like global variables that have been bound permanently to a parameter, so they are accessible from everywhere and can be parameterized anywhere. In Common Lisp, all top-level variables (but not constants or functions) are dynamic, and conventionally are written with an asterisk as a prefix and suffix. In addition, when a variable in a Common LIsp lambda-list (or equivalent) is dynamic, it will be parameterized rather than lexically bound when the procedure is called.
* Are you saying that "Fluid variables" (SRFI 15, withdrawn) is a Scheme port and equivalent of "dynamic variables" in CL?
* If you have a reference to the dynamic variables section in some CL implementation's docs feel free to share.
* The fluid variables implementation in https://srfi.schemers.org/srfi-15/srfi-15.html looks expensive:
Accesses to a fluid variable have no additional overhead over normal variable accesses, but a constant "switching cost" is incurred for each fluid variable you have, at continuation jump time.
This implementation's worst-case overhead is incredibly large. (That is with high number of continuation jumps, high number of fluid variables, and few or no actual fluid variable accesses.)
I think compared to this one I prefer the current Gambit parameter object overhead profile: |parameterize| and load/store have overhead, but there is no switching cost.
* (The fluid variables impl would not work out of the box in Gambit, as Gambit apart from normal continuation jumps, has low level continuation jumps from the green threads which are outside |dynamic-wind|'s scope.)
2) Briefness of syntax
All the examples I have found (except Marc's, thank you Marc) involve using
parameters in exactly this way, which I why I am especially interested in other use cases.
There is a problem with parameters: if a procedure implicitly depends on a parameter, and you want to use the procedure in a "localized" way, then binding the parameter around the whole or a substantial part of a program is convenient. But to use such a procedure in a "universal" way independent of its context, it is necessary to wrap *every* call in a parameterize expression.
Agreed. This is fine. If typing out (parameterize ((parameter-object value) …) (procedure …)) over and over would be a problem, a shorthand syntax could be devised e.g. (§ parameter-object: value procedure …) , implementable as a macro.
3) Faster ways?
* Did you have a thought with bringing up fluid/dynamic variables, to make a point along the lines that significant cost reductions to parameter objects may be possible, for a slightly narrower definition of parameter object?
* I'd be curious if a "global fixed parameter object" could somehow be made to avoid btree/hashtable lookups. Wildguessing, having a vector allocation or copy at |parameterize| should be fine shouldn't it, and also having the first parameter object load/store operation within a |parameterize| _only_, do some kind of punch-through resolve, should be fine also shouldn't it.
Anyhow luckily computers are fast and getting faster, btree/hashtable structures for a couple hundred elements should tend to fit in the L1 cache.
Looking forward to his benchmarks later.
Thanks again for taking on the conversation. Was curious about the performance overhead because I find them so useful per the previous teleport hr example.
Best regards, Adam
For completeness, I asked a Common Lisper about CL dynamic variables. Here is how CL dynamic variables work:
* In CL, all global variables are "dynamic", and they're defined using |defvar|.
* By convention their names are prefixed and suffixed by stars, which is not required by the language.
* In the place of Scheme |parameterize|, CL uses ordinary |let|.
I.e., CL |let| when binding the name of a global variable means |parameterize|, and otherwise means |let|.
* In the place of the procedure call as accessor ("(myparam)"), CL uses normal variable load ("myparam").
* I wildguess a condition that makes it easier for CL to accommodate this syntax without incredible overhead, is the fact that they do not have first class continuations (but only escape continuations which mean N-step procedure return), so they don't have the question "which dynamic variable assignment should apply in which continuation".
He also confirmed that in CL, |let| of a dynamic variable, *implies backup of the variable's previous value, and at return time, restore of the variable to that previous value*. This restore is carried out also in case of "abnormal termination", example: (tagbody (let ((*v* 234)) (go a)) a) or (tagbody (let ((*v* 234)) (print "hello") (go a) (print "hi")) a), go means non-local control transfer (goto).
Example 1, prints out "123": (defvar *v*) ; <- Define dynamic variable () (defun a () (let ((*v* 123)) ; <- Note, let on a dynamic variable means |parameterize| (b))) (defun b () (c)) (defun c () (print *v*)) ; <- Use, same syntax as normal variable access.
Example 2, prints out "123234": (defvar *v*) ; <- Define dynamic variable () (defun a () (let ((*v* 123)) ; <- Note, let on a dynamic variable means |parameterize| (d) (b))) (defun b () (let ((*v* 234)) (c))) (defun c () (d)) (defun d () (print *v*)) ; <- Use, same syntax as normal variable access.
Reference is the "Common Lisp HyperSpec" produced by LispWorks: http://www.lispworks.com/reference/HyperSpec/Body/03_abaab.htm
For completeness, I asked a Common Lisper about CL dynamic variables. Here is how CL dynamic variables work:
All correct.
In addition to defvar and defparameter (which are almost synonymous) you can also make ad-hoc dynamic variables with (declare (special my-var)):
(defun special-test () (labels ((print-value-of-foo () (declare (special foo)) (print foo))) (let ((foo 123)) (declare (special foo)) (print-value-of-foo))))
(defvar a nil)
(defun special-test-2 () (labels ((print-value-of-a () (print a) ;; These would not work since b and c are lexical: ;; ;;(print b) ;;(print c) )) (multiple-value-bind (a b c) (values 1 2 3) (print-value-of-a))))
CLHS: http://www.lispworks.com/documentation/HyperSpec/Body/d_specia.htm#special
CL also has a standard concept of dynamic extent (dynamic scope is for bindings; dynamic extent is for values), as well as a declaration to go with it: http://www.lispworks.com/documentation/HyperSpec/Body/d_dynami.htm#dynamic-extent
E.g. with-open-file (http://www.lispworks.com/documentation/HyperSpec/Body/m_w_open.htm): "The stream object to which the stream variable is bound has dynamic extent; its extent ends when the form is exited."
On Mon, Mar 9, 2020 at 12:57 AM Adam adam.mlmb@gmail.com wrote:
* I wildguess a condition that makes it easier for CL to accommodate this
syntax without incredible overhead, is the fact that they do not have first class continuations (but only escape continuations which mean N-step procedure return), so they don't have the question "which dynamic variable assignment should apply in which continuation".
That's correct. The traditional implementations are:
"deep binding": a global alist holding all the bindings of all dynamic variables which is pushed when a let or lambda that binds dynamic variables is entered, and that is popped when it exits
"shallow binding": either a similar setup but with the current value cached in the symbol representing the variable at run time, or a setup with one alist per variable.
Neither of these methods works correctly in the presence of call/cc or threads. Threads are only semi-standardized in CL: < https://trac.common-lisp.net/bordeaux-threads/wiki/ApiDocumentation%3E. If you use this library, you still have to deal with either the "reinitialize local bindings" or the "copy local bindings" strategy of the underlying application: the "share bindings with the parent thread" strategy is hidden (all per #lisp).
John Cowan http://vrici.lojban.org/~cowan cowan@ccil.org It's like if you meet an really old, really rich guy covered in liver spots and breathing with an oxygen tank, and you say, "I want to be rich, too, so I'm going to start walking with a cane and I'm going to act crotchety and I'm going to get liver disease. --Wil Shipley
On Mar 8, 2020, at 11:22 PM, Adam adam.mlmb@gmail.com wrote:
- Faster ways?
Did you have a thought with bringing up fluid/dynamic variables, to make a point along the lines that significant cost reductions to parameter objects may be possible, for a slightly narrower definition of parameter object?
I'd be curious if a "global fixed parameter object" could somehow be made to avoid btree/hashtable lookups. Wildguessing, having a vector allocation or copy at |parameterize| should be fine shouldn't it, and also having the first parameter object load/store operation within a |parameterize| _only_, do some kind of punch-through resolve, should be fine also shouldn't it.
Anyhow luckily computers are fast and getting faster, btree/hashtable structures for a couple hundred elements should tend to fit in the L1 cache.
Looking forward to his benchmarks later.
Thanks again for taking on the conversation. Was curious about the performance overhead because I find them so useful per the previous teleport hr example.
Best regards, Adam
The implementation of parameter objects has much in common with the implementation of thread local storage. For the most efficient implementation the compiler would need to know the set of all parameter objects that are used in the whole program. So if you combine “a fixed set of parameter objects defined by each module/library” and “an intelligent static linker”, then you could implement parameter objects more efficiently with a variety of methods:
1) if the number of parameter objects (P) is small, put their binding in the “dynamic environment” object (which is essentially a vector of P cells) and this vector is functionnaly updated (copied) when you enter a parameterize.
2) otherwise create a K-ary tree containing the P cells at the leaves, which allows to only copy the path leading to the cell of the parameter object to be bound, in time O(K * log_K(P)).
3) if you know that some are more frequently accessed, then give them priority by using an unbalanced tree (similar to a Huffman coding tree).
And I’m sure there are others. But having a fixed number of parameter objects and static linking is incompatible with all the niceties offered by dynamic languages, for example it precludes the port timeout handling I mentionned previously (where you have to allocate a new parameter object whenever you create a new port).
Marc
On Mar 8, 2020, at 10:56 AM, John Cowan cowan@ccil.org wrote:
On Sun, Mar 8, 2020 at 10:44 AM Adam adam.mlmb@gmail.com wrote:
Parameter objects are very practical and have ample use scenarios, both high-level and relatively low-level.
I would be interested in hearing about these use cases. In particular, parameters (unlike fluid/dynamic variables) are first-class: they can be stored in local variables or data structures. If you have any use cases of this kind, I very much would like to know.
Here’s a case that I find interesting that I have considered for Gambit but not implemented yet.
All Gambit input ports have a timeout setting which specifies when a port “read” that blocks will time-out. The time-out handler can be set with a call such as this:
(input-port-timeout-set! port 5 (lambda () (pretty-print 'time-out) #f)) (read port)
Here the time-out handler will be called if the (read port) doesn’t return within the next 5 seconds. The #f returned by the handler will cause the read to return an end-of-file (otherwise the operation will be retried).
Although the timeout feature is very useful it has the problem that there is just one timeout per port and the API mutates the port structure. It would be better to have one timeout per thread (and a different API to specify the timeout), so that different threads can specify a timeout that suits their needs. This is where a parameter object would be useful.
Each port would have a timeout parameter object attached to it and a thread would do something like:
(parameterize (((input-port-timeout port) (make-timeout-handler 5 (lambda () …)))) (read port))
The scope of this timeout would only apply to that port, in the current thread and in the scope of the parameterize.
I think that is a better API.
Marc
The dynamic environment is implemented as a hybrid structure that combines a dictionary (implemented as a binary tree), with a special case for the frequently accessed parameters (current-input-port, etc), and a cache of the last 3 parameters that were accessed.
So if you access the current input port or one of the last 3 accessed parameters, the time complexity is O(1). Otherwise it is O(log N) where N is the number of bindings in the environment.
I may change the binary tree to a table once the functional API to tables is fully implemented.
So I think the complexity is pretty good, but obviously much slower than an access to a lexical variable. I’ll let you do some benchmarking to determine the hidden constant.
Marc
On Mar 8, 2020, at 10:44 AM, Adam adam.mlmb@gmail.com wrote:
Hi Marc,
I can't recall if this is somewhere in the mailing list archive from before:
I recall that you indicated that parameter objects are expensive.
Would you mind describing/quantifying the average/worst case/best case cost for assigning a value to a parameter object by parameterization (|parameterize|), and for accessing a parameter object (load or store operation by invoking it as a closure)?
Parameter objects are very practical and have ample use scenarios, both high-level and relatively low-level.
Thanks, Adam
Marc, I'd like to collect all your explanations about Gambit's internals into one place if it's OK with you. It would be enlightening for anyone wishing to optimize code or to learn more about how advanced dynamic language implementations work.
I can try to find and copy/paste some of the stuff you've written so far in emails and GitHub issues.
A complete guide comparing and contrasting the approaches of the major Scheme implementations would be even more enlightening. Gambit is the leading implementation in r7rs-benchmarks so it could even make for good PR :) Meanwhile, it could foster some friendly competition between implementations. And it would be a very valuable index for the larger CS community on what kind of performance can be achieved with particular levels of implementation complexity.
That would be super interesting! +1 to that initiative!
iain
On Sun, Mar 8, 2020 at 8:14 AM Lassi Kortela lassi@lassi.io wrote:
Marc, I'd like to collect all your explanations about Gambit's internals into one place if it's OK with you. It would be enlightening for anyone wishing to optimize code or to learn more about how advanced dynamic language implementations work.
I can try to find and copy/paste some of the stuff you've written so far in emails and GitHub issues.
A complete guide comparing and contrasting the approaches of the major Scheme implementations would be even more enlightening. Gambit is the leading implementation in r7rs-benchmarks so it could even make for good PR :) Meanwhile, it could foster some friendly competition between implementations. And it would be a very valuable index for the larger CS community on what kind of performance can be achieved with particular levels of implementation complexity.
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://mailman.iro.umontreal.ca/cgi-bin/mailman/listinfo/gambit-list
Lassi, +1.
What about you simply put this in the Wiki?
There are attempts at describing stuff in the Wiki, you can build on and rework that.
Adam
On Sun, 8 Mar 2020 at 23:14, Lassi Kortela lassi@lassi.io wrote:
Marc, I'd like to collect all your explanations about Gambit's internals into one place if it's OK with you. It would be enlightening for anyone wishing to optimize code or to learn more about how advanced dynamic language implementations work.
I can try to find and copy/paste some of the stuff you've written so far in emails and GitHub issues.
A complete guide comparing and contrasting the approaches of the major Scheme implementations would be even more enlightening. Gambit is the leading implementation in r7rs-benchmarks so it could even make for good PR :) Meanwhile, it could foster some friendly competition between implementations. And it would be a very valuable index for the larger CS community on what kind of performance can be achieved with particular levels of implementation complexity.
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://mailman.iro.umontreal.ca/cgi-bin/mailman/listinfo/gambit-list
Indeed that would be a nice documentation to have! Perhaps some day I’ll write an academic paper that collects all the Gambit optimizations and tricks, but not now as this represents a lot of work (each backend has its own set of tricks, and there’s more than C… now x86, ARM, riscv, JavaScript, Python, PHP, Go, …). You’ll find bits and pieces in the papers I have written over the years, but beware of “bit rot” (in the papers).
You might want to coordinate with Adam who has started collecting implementation information on the wiki.
Marc
On Mar 8, 2020, at 11:14 AM, Lassi Kortela lassi@lassi.io wrote:
Marc, I'd like to collect all your explanations about Gambit's internals into one place if it's OK with you. It would be enlightening for anyone wishing to optimize code or to learn more about how advanced dynamic language implementations work.
I can try to find and copy/paste some of the stuff you've written so far in emails and GitHub issues.
A complete guide comparing and contrasting the approaches of the major Scheme implementations would be even more enlightening. Gambit is the leading implementation in r7rs-benchmarks so it could even make for good PR :) Meanwhile, it could foster some friendly competition between implementations. And it would be a very valuable index for the larger CS community on what kind of performance can be achieved with particular levels of implementation complexity.
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://mailman.iro.umontreal.ca/cgi-bin/mailman/listinfo/gambit-list
Indeed that would be a nice documentation to have! Perhaps some day I’ll write an academic paper that collects all the Gambit optimizations and tricks, but not now as this represents a lot of work (each backend has its own set of tricks, and there’s more than C… now x86, ARM, riscv, JavaScript, Python, PHP, Go, …). You’ll find bits and pieces in the papers I have written over the years, but beware of “bit rot” (in the papers).
That's a great idea. That would probably make for a pretty massive paper by this point, even if it doesn't go into detail.
Can you gather an approximate bullet-point list of the current techniques? It could be just a simple text file. The rest of us can fill in the details and ask questions if/when we get stuck.
You might want to coordinate with Adam who has started collecting implementation information on the wiki.
Sure. Is it this page: http://gambitscheme.org/wiki/index.php/Internal_Documentation?
What's your stance on having Gambit information in an aggregated guide to the internals of different Scheme/Lisp implementations? I'd like to have something that summarizes the GC/memory management techniques, thread implementations and object representations used by different Schemes. A basic breakdown of compiler optimizations would be useful as well. As said, Gambit's internals ought to compare very favorably to other implementations.
On Sat, Mar 21, 2020 at 12:20:15AM +0200, Lassi Kortela wrote:
Indeed that would be a nice documentation to have! Perhaps some day I’ll write an academic paper that collects all the Gambit optimizations and tricks, but not now as this represents a lot of work (each backend has its own set of tricks, and there’s more than C… now x86, ARM, riscv, JavaScript, Python, PHP, Go, …). You’ll find bits and pieces in the papers I have written over the years, but beware of “bit rot” (in the papers).
Surely not more work than writing the implementations in the first place?
That's a great idea. That would probably make for a pretty massive paper by this point, even if it doesn't go into detail.
Can you gather an approximate bullet-point list of the current techniques? It could be just a simple text file. The rest of us can fill in the details and ask questions if/when we get stuck.
You might want to coordinate with Adam who has started collecting implementation information on the wiki.
Sure. Is it this page: http://gambitscheme.org/wiki/index.php/Internal_Documentation?
Perhaps on a wiki, seeded with the information already available?
What's your stance on having Gambit information in an aggregated guide to the internals of different Scheme/Lisp implementations? I'd like to have something that summarizes the GC/memory management techniques, thread implementations and object representations used by different Schemes. A basic breakdown of compiler optimizations would be useful as well. As said, Gambit's internals ought to compare very favorably to other implementations.
This would be very useful.
-- hendrik
Hi Marc,
Cool, wonderful!
Yeah for any situation like
(define (a) ; Teleport from hr (b)) (define (b) (c)) (define (c) ; Teleport to hr #!void)
, just totally great.
Your subsequent email re. "(make-timeout-handler 5 (lambda () …))))" usecase and "I think that is a better API." comment I believe I fully agree with, also that's neat, it illustrates how this is useful on the runtime-interacts-with-userland level.
Thinking back, all my parameter objects until now have been global variables. This also means that, for a given sourcecode file, the number of parameter objects has been pre-specified. In this kind of usage situation, would any optimization trick be possible? E.g. could the "current input port or one of the last 3 accessed parameters" set be extended with a copy of each of that fixed set of toplevel parameter objects and so accordingly their parameterization and access be O(1) always.
(This email was primarily to learn what kind of time characteristics parameterized objects have, given how great they are.)
Adam
On Sun, 8 Mar 2020 at 23:00, Marc Feeley feeley@iro.umontreal.ca wrote:
The dynamic environment is implemented as a hybrid structure that combines a dictionary (implemented as a binary tree), with a special case for the frequently accessed parameters (current-input-port, etc), and a cache of the last 3 parameters that were accessed.
So if you access the current input port or one of the last 3 accessed parameters, the time complexity is O(1). Otherwise it is O(log N) where N is the number of bindings in the environment.
I may change the binary tree to a table once the functional API to tables is fully implemented.
So I think the complexity is pretty good, but obviously much slower than an access to a lexical variable. I’ll let you do some benchmarking to determine the hidden constant.
Marc
On Mar 8, 2020, at 10:44 AM, Adam adam.mlmb@gmail.com wrote:
Hi Marc,
I can't recall if this is somewhere in the mailing list archive from
before:
I recall that you indicated that parameter objects are expensive.
Would you mind describing/quantifying the average/worst case/best case
cost for assigning a value to a parameter object by parameterization (|parameterize|), and for accessing a parameter object (load or store operation by invoking it as a closure)?
Parameter objects are very practical and have ample use scenarios, both
high-level and relatively low-level.
Thanks, Adam
Hi Adam,
On Sun, 8 Mar 2020 23:26:41 +0800 Adam adam.mlmb@gmail.com wrote:
Hi Marc,
Cool, wonderful!
Yeah for any situation like
(define (a) ; Teleport from hr (b)) (define (b) (c)) (define (c) ; Teleport to hr #!void)
, just totally great.
Before you fall too deeply in love with parameters, beware that heavily parameterized code can easily become hard to read:
$ gsi
(define p-a (make-parameter 46)) (define p-b (make-parameter 23)) (define p-op (make-parameter -))
(define (doit!) (let ((op (p-op)) (a (p-a)) (b (p-b))) (display `("calculating ( " ,op " ) applied to" ,a "and" ,b)) (newline) (op a b)))
(parameterize ((p-op *) (p-a 21) (p-b 2)) (doit!))
;; => ;; (calculating ( #<procedure #11 *> ) applied to 21 and 2) ;; 42
;; Great!
(load "magic.scm") ;; attached file magic.scm
(define dare! (magic doit!))
(parameterize ((p-op *) (p-a 21) (p-b 2)) (dare!)) ;; => ;; (calculating ( #<procedure #4 -> ) applied to 46 and 23) ;; 23
;; YMMV, but I find this not exactly intuitive.
Regards
/Jörg
On Mar 10, 2020, at 6:41 PM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
;; YMMV, but I find this not exactly intuitive.
On the contrary I find it very intuitive! I give an example of why this semantics is what you want in the paper “A Better API for First-Class Continuations” section 1.3 (https://www.researchgate.net/publication/2405339_A_Better_API_for_First-Clas...).
Marc
I don't know that anyone is interested in implementing it, but I would be grateful if Gambit implemented delimited control, with continuation marks and delimited parameter bindings. Then, the value of a parameter potentially depends not just on "the" continuation, but on the list of currently active continuation frames, which can grow and shrink non-monotonically due to delimited control operators. http://okmij.org/ftp/Computation/dynamic-binding.html
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org We could have an obfuscated-scheme contest, but if call/cc and macros are allowed, that would be like using nuclear bombs to hunt bunnyrabbits... — Ray Dillinger
On Tue, Mar 10, 2020 at 9:19 PM Marc Feeley feeley@iro.umontreal.ca wrote:
On Mar 10, 2020, at 6:41 PM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
;; YMMV, but I find this not exactly intuitive.
On the contrary I find it very intuitive! I give an example of why this semantics is what you want in the paper “A Better API for First-Class Continuations” section 1.3 (https://www.researchgate.net/publication/2405339_A_Better_API_for_First-Clas...).
Marc
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://mailman.iro.umontreal.ca/cgi-bin/mailman/listinfo/gambit-list
On Wed, Mar 11, 2020 at 02:32:17AM -0400, Faré wrote:
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org We could have an obfuscated-scheme contest, but if call/cc and macros are allowed, that would be like using nuclear bombs to hunt bunnyrabbits... — Ray Dillinger
Indeed. Guy Steele pointed out long ago that continuations had all the power of go-to's, and used that in Rabbit, his Scheme compiler. Yes, a bunnyrabbit.
And as for nuclear bombs, continuations hav all the problems of go-to's as well. The only reason we tolerate this is that continuations are much, much more useful.
-- hendrik
Fare if you want to discuss this please start a separate thread, maybe offer some pseudocode to illustrate utility :)
On Wed, 11 Mar 2020 at 14:32, Faré fahree@gmail.com wrote:
I don't know that anyone is interested in implementing it, but I would be grateful if Gambit implemented delimited control, with continuation marks and delimited parameter bindings. Then, the value of a parameter potentially depends not just on "the" continuation, but on the list of currently active continuation frames, which can grow and shrink non-monotonically due to delimited control operators. http://okmij.org/ftp/Computation/dynamic-binding.html
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org We could have an obfuscated-scheme contest, but if call/cc and macros are allowed, that would be like using nuclear bombs to hunt bunnyrabbits... — Ray Dillinger
Yes delimited continuations is on my wishlist too!
Marc
On Mar 11, 2020, at 2:32 AM, Faré fahree@gmail.com wrote:
I don't know that anyone is interested in implementing it, but I would be grateful if Gambit implemented delimited control, with continuation marks and delimited parameter bindings. Then, the value of a parameter potentially depends not just on "the" continuation, but on the list of currently active continuation frames, which can grow and shrink non-monotonically due to delimited control operators. http://okmij.org/ftp/Computation/dynamic-binding.html
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org We could have an obfuscated-scheme contest, but if call/cc and macros are allowed, that would be like using nuclear bombs to hunt bunnyrabbits... — Ray Dillinger
On Tue, Mar 10, 2020 at 9:19 PM Marc Feeley feeley@iro.umontreal.ca wrote:
On Mar 10, 2020, at 6:41 PM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
;; YMMV, but I find this not exactly intuitive.
On the contrary I find it very intuitive! I give an example of why this semantics is what you want in the paper “A Better API for First-Class Continuations” section 1.3 (https://www.researchgate.net/publication/2405339_A_Better_API_for_First-Clas...).
Marc
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://mailman.iro.umontreal.ca/cgi-bin/mailman/listinfo/gambit-list
On Tue, 10 Mar 2020 21:19:01 -0400 Marc Feeley feeley@iro.umontreal.ca wrote:
On Mar 10, 2020, at 6:41 PM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
;; YMMV, but I find this not exactly intuitive.
On the contrary I find it very intuitive!> I give an example of why this semantics is what you want in the paper “A Better API for First-Class Continuations” section 1.3 (https://www.researchgate.net/publication/2405339_A_Better_API_for_First-Clas...).
Note. I'm not saying that it would be wrong by any means.
Just warn that there is an assumption I'd say is "intuitive" (at least for the novice): that "parameterize" is somewhat similar to "let", i.e., "values are bound within". They are just different scope and secretly unwound behind the szene.
In practice I'm much easier confused understanding code with dynamic scope applies than code where lexical scope rules.
Jörg
On Mar 11, 2020, at 7:39 AM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
On Tue, 10 Mar 2020 21:19:01 -0400 Marc Feeley feeley@iro.umontreal.ca wrote:
On Mar 10, 2020, at 6:41 PM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
;; YMMV, but I find this not exactly intuitive.
On the contrary I find it very intuitive!> I give an example of why this semantics is what you want in the paper “A Better API for First-Class Continuations” section 1.3 (https://www.researchgate.net/publication/2405339_A_Better_API_for_First-Clas...).
Note. I'm not saying that it would be wrong by any means.
Just warn that there is an assumption I'd say is "intuitive" (at least for the novice): that "parameterize" is somewhat similar to "let", i.e., "values are bound within". They are just different scope and secretly unwound behind the szene.
In practice I'm much easier confused understanding code with dynamic scope applies than code where lexical scope rules.
Jörg
As Hendrik points out, the complexity is brought about (or exposed) by continuations.
The same can be said of letrec which can behave unintuitively when continuations are used. It means that these two expressions are not equivalent:
(let ((v (f x))) …) (letrec ((v (f x))) …)
because the use of continuations can reveal the “set!” that is part of the semantics of letrec.
Marc
On Wed, 11 Mar 2020 10:07:09 -0400 Marc Feeley feeley@iro.umontreal.ca wrote:
As Hendrik points out, the complexity is brought about (or exposed) by continuations.
The same can be said of letrec which can behave unintuitively
I can't agree more.
These are all those details, which need to be explained when justifying the summary that "parameters are expensive". The runtime lookup cost by itself may easily become negligible.
Jörg
On Wed, 11 Mar 2020 at 19:39, Jörg F. Wittenberger < Joerg.Wittenberger@softeyes.net> wrote:
On Tue, 10 Mar 2020 21:19:01 -0400 Marc Feeley feeley@iro.umontreal.ca wrote:
On Mar 10, 2020, at 6:41 PM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
;; YMMV, but I find this not exactly intuitive.
On the contrary I find it very intuitive!> I give an example of why this semantics is what you want in the paper “A Better API for First-Class Continuations” section 1.3 (
https://www.researchgate.net/publication/2405339_A_Better_API_for_First-Clas... ).
Note. I'm not saying that it would be wrong by any means.
[...]
In practice I'm much easier confused understanding code with dynamic
scope applies than code where lexical scope rules.
As for me I find the parameter objects highly intuitive.
Detail discussion:
With that said, if you need to solve a primary language problem than parameter objects were designer for, e.g. normal function value arguments, then of course parameters are not needed.
The teleport example I included in my third post in this thread, reflects what I use parameter objects for. Again:
(define (a)
; Teleport from hr (b)) (define (b) (c)) (define (c) ; Teleport to hr #!void)
This is the same usecase as Marc uses them for in the runtime. That is, you have a chain of procedure calls where including a hundred arguments would be too verbose (or calls go through "someone else's code" where you can't even add arguments), and for this reason you use parameter objects, which you assign at the beginning of the call chain (by |parameterize|) and then use at the end of the call chain (by calling the respective parameter object), and the utility is that none of the intermediary procedures need to define those values as arguments (e.g. in the example above, the |b| procedure does not need to define as arguments the values teleported from |a| to |c|).
The |doit!| example you provided does not illustrate this particular utility, but some other utility. On a quick read cannot immediately comprehend what your |magic| procedure does so can't comment on it. I do see call/cc calls inside |magic|'s definition however and I agree that the combination of parameter objects and call/cc would need additional consideration (unclear of details in this moment). call/c is not so frequently used though and its use does mean that particular considerations need to be made for all associated code anyhow though so I don't find it to steal from the teleport utility per above. If you need parameter objects that work some other way than the ordinary Gambit/Scheme parameter objects, just by all means implement that functionality yourself.
Adam
Am Thu, 12 Mar 2020 14:44:03 +0800 schrieb Adam adam.mlmb@gmail.com:
On Wed, 11 Mar 2020 at 19:39, Jörg F. Wittenberger < Joerg.Wittenberger@softeyes.net> wrote: [...]
In practice I'm much easier confused understanding code with dynamic scope applies than code where lexical scope rules.
As for me I find the parameter objects highly intuitive.
YMMV.
I must admit that I did not quite understand all of your remarks:
The teleport example I included in my third post in this thread, reflects what I use parameter objects for. Again:
(define (a)
; Teleport from hr (b)) (define (b) (c)) (define (c) ; Teleport to hr #!void)
This for instance. Which "hr" you mean?
including a hundred arguments would be too verbose
Recall, when assigning "hundreds of arguments" to parameters, that parameters are expensive. (Can you, e.g., group them into records?)
arguments (e.g. in the example above, the |b| procedure does not need to define as arguments the values teleported from |a| to |c|).
The |doit!| example you provided does not illustrate this particular utility,
Sorry for that. I actually meant "doit!" to show that a 0-ari procedure whould have access to parameters bound by `parameterize`. However I badly failed to put an intermediate procedure in between.
Nevertheless: the "doit!" should do the expected thing. The "dare!" was meant to look incredibly simillar and still _appear_ to ignore the parameterize. (However: If you where to follow the dynamic-wind chain, you'd see that the bindings are unwound for the "dare!"-call and rewound afterwards.)
comprehend what your |magic| procedure does so can't comment on it.
Here a slight rewrite:
(define (call-with-current-dynamic-extent proc) ;; patterned after call/cc (proc (call-with-current-continuation (lambda (return) (call-with-values (lambda () (call-with-current-continuation (lambda (c) (return (lambda (thunk) (call-with-current-continuation (lambda (k) (c k thunk)))))))) (lambda (k thunk) (call-with-values thunk k)))))))
(define (dynamic f) ;; convert `f` into a version which restores the dynamic envt first (call-with-current-dynamic-extent (lambda (dynamic-extent) (lambda args (dynamic-extent (lambda () (apply f args)))))))
(define magic dynamic)
additional consideration (unclear of details in this moment). call/c is not so frequently used though and its use does mean that
Except, maybe, in exception handling etc.
Best
Jörg