call/ccでジェネレータを実装
最初に特化したジェネレータを返す関数を実装し、それを一般化する
数値をインクリメントしつつ無限ループする関数を返す関数を書く
(define (make-counter) (lambda () (let rec ((c 0)) (rec (+ c 1)))))
これに途中で中断できるように call/cc を追加する
(define (make-counter) (lambda () (call/cc (lambda (return) (let rec ((c 0)) (return c) (rec (+ c 1)))))))
また再開できるように call/cc を追加し、継続を保存して値を返す
(define (make-counter) (let ((restart #f)) (lambda () (call/cc (lambda (return) (let rec ((c 0)) (call/cc (lambda (restart-cont) (set! restart restart-cont) (return c))) (rec (+ c 1))))))))
2回目以降の呼び出しは再開用継続を呼び出すようにする
(define (make-counter) (let ((restart #f)) (lambda () (if restart (restart) (call/cc (lambda (return) (let rec ((c 0)) (call/cc (lambda (restart-cont) (set! restart restart-cont) (return c))) (rec (+ c 1)))))))))
またreturn用継続を呼び出しごとに更新する
(define (make-counter) (let ((return #f ) (restart #f)) (lambda () (call/cc (lambda (return-cont) (set! return return-cont) (if restart (restart) (let rec ((c 0)) (call/cc (lambda (restart-cont) (set! restart restart-cont) (return c))) (rec (+ c 1)))))))))
これで数値をインクリメントしつつ返すジェネレータを実装できたので、これを一般化する
ジェネレータ本体部分で再開用継続を保存しつつ値を返せればいいので、これをクロージャにして引数として本体に渡して完成
(define (make-generator proc) (let ((return #f) (restart #f)) (lambda () (call/cc (lambda (return-cont) (set! return return-cont) (if restart (restart) (proc (lambda (x) (call/cc (lambda (cc) (set! restart cc) (return x)))))))))))
使い方
(define counter (make-generator (lambda (yield) (let rec ((c 0)) (yield c) (rec (+ c 1)))))) (counter) ;; => 0 (counter) ;; => 1 (counter) ;; => 2