コルーチン2(call/ccでREPL)

プログラミングGaucheのpp296-298。今回はコルーチンでREPLを実装する。もちろんread、eval、printの各処理はGaucheに丸投げだけどな!それは実装とは言わないッ!
実行結果がこんな感じになるように、3つのコルーチンreader、evaluator、printerを作成する。

gosh> (coroutine-init! evaluator printer)
#<undef>
gosh> (reader)
coroutine> 123
READ(1): 123
EVAL(1): 123
PRINT(1): 123
coroutine> "Hello, co-routine world!"
READ(2): Hello, co-routine world!
EVAL(2): Hello, co-routine world!
PRINT(2): Hello, co-routine world!
coroutine> (+ 1 2 3)
READ(3): (+ 1 2 3)
EVAL(3): 6
PRINT(3): 6
coroutine> (print "OK")
READ(4): (print OK)
OK
EVAL(4): #<undef>
PRINT(4): #<undef>
coroutine> (read)
(+ 1 2 3)
READ(5): (read)
EVAL(5): (+ 1 2 3)
PRINT(5): (+ 1 2 3)
coroutine> (call/cc (lambda (c)
		      (print 'one)
		      (print 'two)
		      (c 100)
		      (print 'four)))
READ(6): (call/cc (lambda (c) (print 'one) (print 'two) (c 100) (print 'four)))
one
two
EVAL(6): 100
PRINT(6): 100
coroutine> (exit)
READ(7): (exit)

Process scheme finished

まずcoroutine-init!で、readerの次に呼ばれるべきevaluatorとprinterを登録している。*tasks*はキューなので、dequeue!すると必ず登録した順に取り出せる。んで、(reader)を評価すると(yield)の評価でコルーチンを抜けながらevaluator、printer、reader、evaluator、printer、...と処理を渡り歩く。
実行例のように「croutine> 」というプロンプトの後に式を入力すると

  • readによるS式の読込み
  • evalによる読込んだS式の評価
  • printによる評価結果の出力

を担うコルーチンによって各処理が実行され、結果がprintされる。evaluatorでのprintとprinterでのprintが同じ結果を表示するのは当たり前かな。ってかprinter無くても良くね?って思ったけど、三つ巴コルーチンであることを示すための例なのでprinterも入れたのかな。S式の評価はGaucheに丸投げしているので、最後の(exit)を評価してしまうとGaucheインタプリタが終了してしまっている。


さて、では3つのコルーチンの種明かし。
実行するにはid:yagiey:20100510:1273495027で出てきた*tasks*、define-coroutine、coroutine-init!が必要。

;; コルーチン間でのデータの受け渡しに使う変数
(define *val* #f)

;; コルーチンreader(readはGaucheまかせ)
(define-coroutine (reader yield)
  (define prompt
    (lambda () (format #t "coroutine> ") (flush)))
  (prompt)
  (let lp ((count 1)
	(exp (read)))
    (set! *val* exp)
    (print #`"READ(,count): ,*val*")
    (yield)
    (prompt)
    (lp (+ count 1) (read))))

;; コルーチンevalator(evalはGaucheまかせ)
(define-coroutine (evaluator yield)
  (let lp ((count 1))
    (set! *val* (eval *val* (current-module)))
    (print #`"EVAL(,count): ,*val*")
    (yield)
    (lp (+ count 1))))

;; コルーチンprinter(printはGaucheまかせ)
(define-coroutine (printer yield)
  (let lp ((count 1))
    (set! *val* (print #`"PRINT(,count): ,*val*"))
    (yield)
    (lp (+ count 1))))

formatはCでいうとfprintf的な、書式を指定して(さらに出力先も指定して?)出力する手続きらしい。ここでは特に書式設定していないので、print手続きでも良いかなと思ったけど改行しちゃうからカッコ悪いか。printの改行しないバージョンみたいなの無いのかな?
flush手続きは...、なんかようわからんけどストリーム的なアレをflushしちゃう的な?(ごめんなさい調べてません)
結局printerでset!した値は使ってないみたいだな。っていうかprintの結果は#だから使い道ないっぽい?
手続き「three」と手続き「五」の例が理解できれば問題ない内容だと思う。違う点と言えば、各ルーチンでの結果を*val*という大域変数を介してやり取りする点かな。*val*を介さずにやり取りできないのかな...?とも思うけど。よく分からん。