Wed Jul 23 18:47:50 JST 2014

call/cc, coroutine

goal:

(define (f)
  (yield 0)
  (yield 1)
  (return 2))

return は全体の返り値.そこで終わり.
yield はまだ続きのある、一旦の返り値.

(f)
; => 0
(f)
; => 1
(f)
; => 2
(f)
; => 2
(f)
; => 2

と使えるようになる.

call/cc と自己の書き換えを使う解法

(define (f)
  (let/cc return
          (let/cc br
                 (set! f (lambda () (br)))
                 (return 0))
          (let/cc br
                 (set! f (lambda () (br)))
                 (return 1))
          (return 2)
          ))

return は、(f)からの大域脱出で、

1つ目の br は、

          (let/cc br
                 (set! g (lambda () (br)))
                 (return 1))
          (return 2)

を、2つ目の br は、

          (return 2)

を表す継続.

次のように動く

gosh> (f)
0
gosh> (f)
1
gosh> (f)
2
gosh> (f)
2
gosh> (f)
2

マクロ

そのように書き換えるマクロ

(define-macro (coroutine f . bodies)
  (define (rewrite-yield exp)
    (cond ((and (pair? exp) (equal? (car exp) 'yield))
           `(let/cc br (set! ,f (lambda () (br)))
                    (return ,@(cdr exp))))
          ((list? exp) (print exp) (map rewrite-yield exp))
          (else exp)))
  `(define (,f)
     (let/cc return ,@(map rewrite-yield bodies))))

(coroutine f
  (begin (yield -1) (yield 0))
  (yield 1)
  (return 2))
gosh> (f)
-1
gosh> (f)
0
gosh> (f)
1
gosh> (f)
2
gosh> (f)
2
gosh> (f)
2

無限yield

すごいよくある使い方

(coroutine g
  (let loop ((i 0))
    (yield i)
    (loop (+ i 1)))
  (return #f))
gosh> (g)
0
gosh> (g)
1
gosh> (g)
2
gosh> (g)
3
gosh> (g)
4
gosh>