loopsschemeimplementationmultiple-valuecallcc

call/cc and multiple values


I'm playing around with implementing looping using call/cc and wrote a map function like this:

(define (map1 f l)
  ((lambda (state)
     (let ((cc (car state))
           (l (cadr state))
           (m (caddr state)))
       (if (null? l) (reverse m)
        (cc (list cc (cdr l) (cons (f (car l)) m))))))
   (call-with-current-continuation
    (lambda (kk) (kk (list kk l '()))))))

So far so good, (map1 (lambda (n) (+ n 2)) '(3 5 11 17)) returns (5 7 13 19). Next I wanted to get rid of the list encapsulation in the state argument with multiple values in the following function:

(define (map2 f l)
  (call-with-values
   (lambda () (call-with-current-continuation
               (lambda (kk) (kk (values kk l '())))))
   (lambda (cc l m)
     (if (null? l) (reverse m)
         (cc (values cc (cdr l) (cons (f (car l)) m)))))))

Racket, Chez, Chicken, gauche and scm all give errors that 1 value was expected but 3 received. Interestingly, gambit gives the expected result (the one I expected, that is). What am I missing in my understanding of call-with-values and/or call/cc? Or can someone re-write my map2 so it works?


Solution

  • You were pretty close. Here it is:

    (define (map2 f l)
      (call-with-values
       (lambda () (call-with-current-continuation
                   (lambda (cc) (values cc l '()))))
       (lambda (cc l m)
         (if (null? l) (reverse m)
             (cc cc (cdr l) (cons (f (car l)) m))))))
    
    ;;; > (map2 (lambda (n) (+ n 2)) '(3 5 11 17))
    ;;; '(5 7 13 19)
    

    You do need values in the initial "generator", but not the call to the continuation itself (the linked documentation is Racket's, but it works in Racket's r5rs mode, I checked).

    And you do not need values in the "receiver" lambda's tail call. The three values get fed to the receiver lambda itself, which expects the three arguments.

    Having foo = (lambda (a b c) ...), calling (call-with-values (lambda () (values x y z)) foo) is the same as calling (foo x y z):

    > (call-with-values 
         (lambda () (values 1 2 3))
         (lambda (a b c) (list c b a)))
    '(3 2 1)
    > ((lambda (a b c) (list c b a)) 1 2 3)
    '(3 2 1)
    > 
    

    call-with-values just supplies the values as arguments in the function call. The continuation, then, is just calling the lambda function with the supplied values. Which is what we're doing in the tail call of that same lambda, thus, looping.

    Just as the documentation is saying,

        (call-with-values generator receiver) → any
    
     generator : (-> any)
     receiver : procedure?
    

    Calls generator, and passes the values that generator produces as arguments to receiver. Thus, call-with-values creates a continuation that accepts any number of values that receiver can accept.