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?
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 thatgenerator
produces as arguments toreceiver
. Thus,call-with-values
creates a continuation that accepts any number of values thatreceiver
can accept.