Hello!
I've been writing some tricky code involving continuations, and ran into the following surprising behavior from Gambit v.4.6.2 running on OSX.
I have two versions of "make-jp", that I believe should be equivalent.
Version 1: (define (make-jp block) (let [(return-point '())] (call/cc (lambda (ret) (define (jp x) (call/cc (lambda (ret) (let [(r return-point)] (set! return-point ret) (r x))))) (set! return-point ret) (let [(result (block jp))] (return-point result))))))
Version 2: (define (make-jp block) (let [(return-point '())] (call/cc (lambda (ret) (define (jp x) (call/cc (lambda (ret) (let [(r return-point)] (set! return-point ret) (r x))))) (set! return-point ret) (return-point (block jp))))))
The only difference is the final line:
Version 1: (let [(result (block jp))] (return-point result))
Version 2: (return-point (block jp))
However, I get different behavior when I test with the following code:
Testing Code: (let* [(j '())] (make-jp (lambda (y) (set! j y) (y 0) 10)) (println "GOT: " (j "X")))
Version 1 prints: GOT: 10
Version 2 prints: GOT: X
Out of the two behaviours, I expect the answer given by Version 1.
Can anyone verify whether this is a bug, and whether the latest version also shows this behavior? This fell out of a piece of code I'm using to simulate coroutines for an interpreter that I am programming. Here I've tried to isolate the problem down to as few lines as possible.
Once I get my environment fixed, I will try it on the latest version of Gambit as well and see if the problem is still there.
-Patrick
Afficher les réponses par date
I tried your code with Bigloo, Chicken and Gambit. All three systems give the same result, so I doubt there is a bug. Perhaps you can explain what you are trying to achieve.
Marc
On Dec 9, 2013, at 10:10 PM, Patrick Li patrickli.2001@gmail.com wrote:
Hello!
I've been writing some tricky code involving continuations, and ran into the following surprising behavior from Gambit v.4.6.2 running on OSX.
I have two versions of "make-jp", that I believe should be equivalent.
Version 1: (define (make-jp block) (let [(return-point '())] (call/cc (lambda (ret) (define (jp x) (call/cc (lambda (ret) (let [(r return-point)] (set! return-point ret) (r x))))) (set! return-point ret) (let [(result (block jp))] (return-point result))))))
Version 2: (define (make-jp block) (let [(return-point '())] (call/cc (lambda (ret) (define (jp x) (call/cc (lambda (ret) (let [(r return-point)] (set! return-point ret) (r x))))) (set! return-point ret) (return-point (block jp))))))
The only difference is the final line:
Version 1: (let [(result (block jp))] (return-point result))
Version 2: (return-point (block jp))
However, I get different behavior when I test with the following code:
Testing Code: (let* [(j '())] (make-jp (lambda (y) (set! j y) (y 0) 10)) (println "GOT: " (j "X")))
Version 1 prints: GOT: 10
Version 2 prints: GOT: X
Out of the two behaviours, I expect the answer given by Version 1.
Can anyone verify whether this is a bug, and whether the latest version also shows this behavior? This fell out of a piece of code I'm using to simulate coroutines for an interpreter that I am programming. Here I've tried to isolate the problem down to as few lines as possible.
Once I get my environment fixed, I will try it on the latest version of Gambit as well and see if the problem is still there.
-Patrick
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://webmail.iro.umontreal.ca/mailman/listinfo/gambit-list
I am developing the semantics for a formulation of coroutines that I've been working on. In vague-ish english words, make-jp returns a "callable thing", which I am calling a "jump point". Every time that a "jump point" is called, execution jumps to the last place where the jump-point was invoked.
If Bigloo, Chicken, and Gambit all behave the same way then I think you are probably right and that this is proper behavior.
However, this leaves me feeling deeply disturbed. I feel that
(f (g x))
should *always* be absolutely equivalent to:
(let [(temp (g x))] (f temp))
My example indicates that this is not necessarily the case. Are there other situations where this is not true? I suspect that I don't quite understand the interaction between tail-calls and continuations properly.
-Patrick
On Mon, Dec 9, 2013 at 7:50 PM, Marc Feeley feeley@iro.umontreal.ca wrote:
I tried your code with Bigloo, Chicken and Gambit. All three systems give the same result, so I doubt there is a bug. Perhaps you can explain what you are trying to achieve.
Marc
On Dec 9, 2013, at 10:10 PM, Patrick Li patrickli.2001@gmail.com wrote:
Hello!
I've been writing some tricky code involving continuations, and ran into
the
following surprising behavior from Gambit v.4.6.2 running on OSX.
I have two versions of "make-jp", that I believe should be equivalent.
Version 1: (define (make-jp block) (let [(return-point '())] (call/cc (lambda (ret) (define (jp x) (call/cc (lambda (ret) (let [(r return-point)] (set! return-point ret) (r x))))) (set! return-point ret) (let [(result (block jp))] (return-point result))))))
Version 2: (define (make-jp block) (let [(return-point '())] (call/cc (lambda (ret) (define (jp x) (call/cc (lambda (ret) (let [(r return-point)] (set! return-point ret) (r x))))) (set! return-point ret) (return-point (block jp))))))
The only difference is the final line:
Version 1: (let [(result (block jp))] (return-point result))
Version 2: (return-point (block jp))
However, I get different behavior when I test with the following code:
Testing Code: (let* [(j '())] (make-jp (lambda (y) (set! j y) (y 0) 10)) (println "GOT: " (j "X")))
Version 1 prints: GOT: 10
Version 2 prints: GOT: X
Out of the two behaviours, I expect the answer given by Version 1.
Can anyone verify whether this is a bug, and whether the latest version also shows this behavior? This fell out of a piece of code I'm using to simulate coroutines for an interpreter that I am programming. Here I've tried to isolate the problem down to as few lines as possible.
Once I get my environment fixed, I will try it on the latest version of Gambit as well and see if the problem is still there.
-Patrick
Gambit-list mailing list Gambit-list@iro.umontreal.ca https://webmail.iro.umontreal.ca/mailman/listinfo/gambit-list
On Dec 9, 2013, at 11:01 PM, Patrick Li patrickli.2001@gmail.com wrote:
However, this leaves me feeling deeply disturbed. I feel that
(f (g x))
should *always* be absolutely equivalent to:
(let [(temp (g x))] (f temp))
My example indicates that this is not necessarily the case. Are there other situations where this is not true? I suspect that I don't quite understand the interaction between tail-calls and continuations properly.
When evaluation is left-to-right (which is not required by the standard, but is common) the expression (f (g x)) is evaluated as though it was
(let ((t1 f)) ;; get value of f (let ((t2 g)) ;; get value of g (let ((t3 x)) ;; get value of x (let ((t4 (t2 t3))) ;; call (g x) (t1 t4))))) ;; call (f (g x))
The expression (let ((temp (g x))) (f temp)) forces a different order, namely
(let ((t1 g)) ;; get value of g (let ((t2 x)) ;; get value of x (let ((t3 (t1 t2))) ;; call (g x) (let ((t4 f)) ;; get value of f (t4 t3))))) ;; call (f temp)
In your code, this makes a difference because f (which is "return-point" in your code) is being mutated during the evaluation of the call to g.
In your "version 1", the function originally bound to return-point will be called (i.e. the continuation of the function make-jp). In your "version 2", the function bound to return-point by the call to (block jp) will be called (i.e. the continuation of the call (y 0)).
Marc
Ah ha! Thank you Marc for that explanation. It was something so simple all along! I spent a considerable number of hours staring at these few lines today. Sorry for leaping to conclusions regarding the integrity of your code! -Patrick
On Mon, Dec 9, 2013 at 8:29 PM, Marc Feeley feeley@iro.umontreal.ca wrote:
On Dec 9, 2013, at 11:01 PM, Patrick Li patrickli.2001@gmail.com wrote:
However, this leaves me feeling deeply disturbed. I feel that
(f (g x))
should *always* be absolutely equivalent to:
(let [(temp (g x))] (f temp))
My example indicates that this is not necessarily the case. Are there
other situations where this is not true? I suspect that I don't quite understand the interaction between tail-calls and continuations properly.
When evaluation is left-to-right (which is not required by the standard, but is common) the expression (f (g x)) is evaluated as though it was
(let ((t1 f)) ;; get value of f (let ((t2 g)) ;; get value of g (let ((t3 x)) ;; get value of x (let ((t4 (t2 t3))) ;; call (g x) (t1 t4))))) ;; call (f (g x))
The expression (let ((temp (g x))) (f temp)) forces a different order, namely
(let ((t1 g)) ;; get value of g (let ((t2 x)) ;; get value of x (let ((t3 (t1 t2))) ;; call (g x) (let ((t4 f)) ;; get value of f (t4 t3))))) ;; call (f temp)
In your code, this makes a difference because f (which is "return-point" in your code) is being mutated during the evaluation of the call to g.
In your "version 1", the function originally bound to return-point will be called (i.e. the continuation of the function make-jp). In your "version 2", the function bound to return-point by the call to (block jp) will be called (i.e. the continuation of the call (y 0)).
Marc