コルーチン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*を介さずにやり取りできないのかな...?とも思うけど。よく分からん。