ashinn / chibi-scheme

Official chibi-scheme repository
Other
1.21k stars 142 forks source link

Unexpected result using continuations #899

Closed gambiteer closed 1 year ago

gambiteer commented 1 year ago

I tried to test some of the SRFI 231 routines in chibi, and found

heine:~/programs/chibi-scheme> env LD_LIBRARY_PATH=/usr/local/chibi/lib /usr/local/chibi/bin/chibi-scheme
> (import (srfi 231))

(define (pp arg) (display arg) (newline))

(pp 'array-copy)

(let* ((cont #f)
       (call-cont #t)
       (domain (make-interval '#(2 2)))
       (A_ (lambda (i j)
             (call-with-current-continuation
              (lambda (c)
                (if (= i j 0)
                    (set! cont c))
                1))))
       (A (make-array domain A_))
       (array-list '()))
  (set! array-list (cons (array-copy A) array-list))
  (pp 'printing)
  (for-each (lambda (A) (pp (array->list* A))) array-list)
  (if call-cont
      (begin
        (set! call-cont #f)
        (cont 4)))
  #;(pp (apply eq? array-list)))
> > array-copy
> printing
((1 1) (1 1))
printing
((4 1) (1 1))

I was expecting array-list to end up with two elements, as in Gambit:

heine:~/programs/chibi-scheme> gsi
Gambit v4.9.4-173-g1759c656

> (import (srfi 231))
> 
(define (pp arg) (display arg) (newline))
> 
(pp 'array-copy)
array-copy
> 
(let* ((cont #f)
       (call-cont #t)
       (domain (make-interval '#(2 2)))
       (A_ (lambda (i j)
             (call-with-current-continuation
              (lambda (c)
                (if (= i j 0)
                    (set! cont c))
                1))))
       (A (make-array domain A_))
       (array-list '()))
  (set! array-list (cons (array-copy A) array-list))
  (pp 'printing)
  (for-each (lambda (A) (pp (array->list* A))) array-list)
  (if call-cont
      (begin
        (set! call-cont #f)
        (cont 4)))
  #;(pp (apply eq? array-list)))
printing
((1 1) (1 1))
printing
((4 1) (1 1))
((1 1) (1 1))

This may have something more to do with continuations than with SRFI 231.

mnieper commented 1 year ago

A Scheme system is allowed to delimit continuations at the REPL prompt.

Can you run your test as a top-level program instead?

gambiteer commented 1 year ago

How do I do that? I tried

heine:~/programs/chibi-scheme> cat test-cont1.scm 
(import (srfi 231))

(define (pp arg) (display arg) (newline))

(pp 'array-copy)

(let* ((cont #f)
       (call-cont #t)
       (domain (make-interval '#(2 2)))
       (A_ (lambda (i j)
             (call-with-current-continuation
              (lambda (c)
                (if (= i j 0)
                    (set! cont c))
                1))))
       (A (make-array domain A_))
       (array-list '()))
  (set! array-list (cons (array-copy A) array-list))
  (pp 'printing)
  (for-each (lambda (A) (pp (array->list* A))) array-list)
  (if call-cont
      (begin
        (set! call-cont #f)
        (cont 4)))
  #;(pp (apply eq? array-list)))

and

heine:~/programs/chibi-scheme> env LD_LIBRARY_PATH=/usr/local/chibi/lib /usr/local/chibi/bin/chibi-scheme test-cont1.scm 
ERROR on line 3 of file test-cont1.scm: undefined variable: newline
  called from <anonymous> on line 1268 of file /usr/local/chibi/share/chibi/init-7.scm
  called from <anonymous> on line 800 of file /usr/local/chibi/share/chibi/init-7.scm
Searching for modules exporting newline ...
newline is exported by:
  (chibi)
  (scheme base)
  (scheme r5rs)
  (scheme red)
  (scheme small)
mnieper commented 1 year ago

You have to import at least (scheme base) and (scheme write).

gambiteer commented 1 year ago

Thanks, I added

(import (scheme base)
        (scheme write)
        (srfi 231))

at the top of the file and got

heine:~/programs/chibi-scheme> env LD_LIBRARY_PATH=/usr/local/chibi/lib /usr/local/chibi/bin/chibi-scheme test-cont1.scm
array-copy
printing
((1 1) (1 1))
printing
((4 1) (1 1))
ashinn commented 1 year ago

FYI if you want to demonstrate that Chibi's array iterators are not call/cc reentrant you need a dimension 3 or higher array.

gambiteer commented 1 year ago

It appears to have nothing to do with arrays, here's another example:

heine:~/programs/chibi-scheme> env LD_LIBRARY_PATH=/usr/local/chibi/lib /usr/local/chibi/bin/chibi-scheme test-cont2.scm
map
printing
(1 2 3 4)
printing
(4 2 3 4)
heine:~/programs/chibi-scheme> cat test-cont2.scm
(import (scheme base)
        (scheme write))

(define (pp arg) (display arg) (newline))

(pp 'map)

(let* ((cont #f)
       (call-cont #t)
       (arg '(1 2 3 4))
       (f (lambda (x)
            (call-with-current-continuation
             (lambda (c)
               (if (eqv? x 1)
                   (set! cont c))
               x))))
       (lists '()))
  (set! lists (cons (map f arg) lists))
  (pp 'printing)
  (for-each pp lists)
  (if call-cont
      (begin
        (set! call-cont #f)
        (cont 4)))
  #;(pp (apply eq? array-list)))

Gambit gives:

heine:~/programs/chibi-scheme> gsi test-cont2.scm
map
printing
(1 2 3 4)
printing
(4 2 3 4)
(1 2 3 4)

Running in the interpreter, mzscheme gives:

> (define (pp arg) (display arg) (newline))
> (pp 'map)
map
> (let* ((cont #f)
         (call-cont #t)
         (arg '(1 2 3 4))
         (f (lambda (x)
              (call-with-current-continuation
               (lambda (c)
                 (if (eqv? x 1)
                     (set! cont c)
                     'die)
                 x))))
         (lists '()))
    (set! lists (cons (map f arg) lists))
    (pp 'printing)
    (for-each pp lists)
    (if call-cont
        (begin
          (set! call-cont #f)
          (cont 4))
        'die)
    #;(pp (apply eq? array-list)))
printing
(1 2 3 4)
printing
(4 2 3 4)
(1 2 3 4)
die

I don't really know how to run this as a file in Racket, sorry. Edit: Forgot the last line of output from the Racket version, which needed two-arm ifs.

gambiteer commented 1 year ago

You don't need map to see the problem:

heine:~/programs/chibi-scheme> cat test-cont3.scm
(import (scheme base)
        (scheme write))

(define (pp arg) (display arg) (newline))

(pp 'function-call)

(let* ((cont #f)
       (call-cont #t)
       (f (lambda (x)
            (call-with-current-continuation
             (lambda (c)
               (if (eqv? x 'first-entry)
                   (set! cont c)
                   'die)
               x))))
       (list '()))
  (set! list (cons (f 'first-entry) list))
  (pp 'printing)
  (pp list)
  (if call-cont
      (begin
        (set! call-cont #f)
        (cont 're-entry))
      'die)
  #;(pp (apply eq? array-list)))

Chibi runs this as

heine:~/programs/chibi-scheme> env LD_LIBRARY_PATH=/usr/local/chibi/lib /usr/local/chibi/bin/chibi-scheme test-cont3.scm
function-call
printing
(first-entry)
printing
(re-entry)

Gambit gives

heine:~/programs/chibi-scheme> gsi test-cont3.scm
function-call
printing
(first-entry)
printing
(re-entry first-entry)
ashinn commented 1 year ago

It appears to be something to do with the direct continuation of a set! clause. Change the set! to

(let ((tmp (f 'first-entry)))
    (set! list (cons tmp list)))

and it works as expected.

ashinn commented 1 year ago

Ah, mystery solved. This is just a matter of the order of evaluation - Chibi is one of the few implementations which evaluate args right-to-left rather than left-to-right.

Since this continuation is caught in the middle of an application call, it's not defined whether the other arguments (here list) are evaluated before or after the continuation is captured. The rewrite above forces an ordering, making it behave like Gambit, but more simply we can flip the order of args to make it agree:

(set! list (xcons list (f 'first-entry)))

Since the order of evaluation is undefined this is not a bug.

gambiteer commented 1 year ago

Thanks for your explanation, I'm really a beginner at continuations.

When I try the program

heine:~/programs/chibi-scheme> cat test-cont4.scm
(import (scheme base)
        (scheme write))

(define (pp arg) (display arg) (newline))

(pp 'function-call)

(let* ((cont #f)
       (call-cont 4)
       (f (lambda (x)
            (call-with-current-continuation
             (lambda (c)
               (if (eqv? x 'first-entry)
                   (set! cont c)
                   'die)
               x))))
       (list '()))
  (set! list (cons (f 'first-entry) list))
  (pp 'printing)
  (pp list)
  (if (not (zero? call-cont))
      (begin
        (set! call-cont (- call-cont 1))
        (cont 're-entry))
      'die)
  #;(pp (apply eq? array-list)))

I see

heine:~/programs/chibi-scheme> env LD_LIBRARY_PATH=/usr/local/chibi/lib /usr/local/chibi/bin/chibi-scheme  test-cont4.scm
function-call
printing
(first-entry)
printing
(re-entry)
printing
(re-entry)
printing
(re-entry)
printing
(re-entry)

So does that mean the list passed to the set! is always '(), no matter how many times the set! is called?

And, does that mean that the value of list at the initial call, '(), is stored in the continuation, rather than the "box" that contains the value?

I guess I still find this counterintuitive.

gambiteer commented 1 year ago

I get it now, thanks.

Message ID: @.***>

ashinn commented 1 year ago

For easier understanding you can use the xcons version in Gambit and see it print out:

printing
(first-entry)
printing
(re-entry)