ぼくもつくってみた。
ズンかドコをひたすら生成するジェネレータと、その出力を受け取って条件が揃ったらキヨシを返すコンシューマをコルーチンとして実装しました。
Lua のコルーチンはまず普通の関数としてコルーチンの中身を定義します。ジェネレータはこんな感じです。
function zundoko()
local choice = {"ズン", "ドコ"}
while true do
local index = math.random(1, 2)
coroutine.yield(choice[index])
end
end
coroutine.yield()
を使って呼び出し元に値を返します。
コンシューマはこんな感じです。
function kiyoshi(input)
local zun = 0
while true do
if input == "ズン" then
zun = zun + 1
else
if input == "ドコ" and zun >= 4 then
return "キ・ヨ・シ!"
end
zun = 0
end
input = coroutine.yield()
end
end
zun
という変数でズンが何回連続して渡されたかを数えています。
4 回以上ズンが連続した状態でドコを受け取ると、キヨシを return
を使って返して終了します。
それ以外の場合は何もせずに coroutine.yield()
で呼び出し元に処理を戻します。
上で作った関数からコルーチンを生成します。
local zundoko_co = coroutine.create(zundoko)
local kiyoshi_co = coroutine.create(kiyoshi)
メイン部分では zundoko_co
から値を 1 個受け取って kiyoshi_co
に渡します。
kiyoshi_co
が何か返したらそれを出力します。
また、コルーチンは return
で戻ると死亡状態になるので、ここでループが終了するようにしました。
while coroutine.status(kiyoshi_co) ~= "dead" do
local state, input = coroutine.resume(zundoko_co)
print(input)
local state, output = coroutine.resume(kiyoshi_co, input)
if output then
print(output)
end
end
coroutine.yield()
はループの内側からコルーチンの外側に一気に脱出して、後で再びループの内部に戻ってこれるので便利です。
実行すると以下のように出力されます。
localhost:zundoko toru$ lua zundoko.lua
ドコ
ズン
ドコ
ドコ
ドコ
ズン
ズン
ドコ
ズン
ドコ
ズン
ドコ
ズン
ドコ
ドコ
ドコ
ドコ
ドコ
ズン
ドコ
ズン
ズン
ズン
ドコ
ズン
ズン
ズン
ズン
ドコ
キ・ヨ・シ!
localhost:zundoko toru$
Lua で作ったコルーチンとだいたい同じ動きをするように継続を使って実装してみました。 処理系には Gauche を使っています。
まずズンかドコをひたすら出力するジェネレータ部分はこんな感じです。
(define (zundoko cont)
(let loop ((next cont))
(let1 next-cont (call/cc (lambda (inner-cont)
(next (if (< (random-real) 0.5) 'ズン 'ドコ) inner-cont)))
(loop next-cont))))
値を返すための継続手続きを引数にとります。
この手続きの中では名前付き let をつかって無限ループを回しています。
そして、値を生成し引数で受け取った継続手続きを使って値を返しますが、このとき call/cc
を使って自分自身の継続手続きも作って一緒に呼び出し元に渡しています。
次にズンかドコを受け取って内部状態を更新するコンシューマ部分です。
(define (kiyoshi input cont exit)
(let loop ((zun 0)
(next cont))
(let-values (((input next-cont exit)
(call/cc (lambda (inner-cont)
(next inner-cont)))))
(if (eq? input 'ズン)
(loop (+ 1 zun) next-cont)
(if (>= zun 4)
(exit 'キ・ヨ・シ!)
(loop 0 next-cont))))))
ここでも、継続手続きを引数にとりますが、3 番目の引数としてループを終了するための大域脱出用の継続手続きも受け取っています。
input
として 'ズン
を受け取った回数をカウントしています。
'ドコ
を受け取ると 'ズン
の回数は 0 にリセットします。
ここでも、あとからループの途中に戻ってこれるように call/cc
で継続手続きを作って呼び出し元に返しています。
そして 'ズン
を 4 回以上連続して受け取った状態で 'ドコ
を受け取ると、大域脱出用の exit
という手続きを呼び出します。
Lua 版と違うのは、このように値を返す先が複数あるところです。
最後にメイン部分です。ここでも名前付き let で無限ループを実行しています。
(print (call/cc
(lambda (exit)
(let loop ((zun zundoko)
(kiyo kiyoshi))
(let-values (((input new-zun)
(call/cc (^[cont] (zun cont)))))
(print input)
(let1 new-kiyo (call/cc (^[cont] (kiyo input cont exit)))
(loop new-zun new-kiyo)))))))
call/cc
が 3 個もありますが、1 番目は「キ・ヨ・シ!」を出力してプログラムを終了するための大域脱出用の継続手続きを作るためのもので、残りの 2 個はそれぞれジェネレータとコンシューマに渡すためのものです。