で、Haskellで書いたやり方でLuaで書けないかやってみた。
function cycle(x) return function(x, i) i = i or 0 i = x[i + 1] and i + 1 or 1 return i, x[i] end, x, nil end fizz = coroutine.create(function() for k, v in cycle({ "", "", "Fizz" }) do coroutine.yield(v) end end) buzz = coroutine.create(function() for k, v in cycle({ "", "", "", "", "Buzz" }) do coroutine.yield(v) end end) for i = 1, 30 do _, fs = coroutine.resume(fizz) _, bs = coroutine.resume(buzz) s = fs .. bs; print((s == "") and i or s) end
無駄にめんどくさくなった気がする。
明日の自分のための解説
まず、cycle関数。
と、その前に。汎用forループを説明しておかないと。
Luaの汎用forループは、次のような形をしています。
for key, value in テーブルとインデクスを受け取りキーとバリューを返す関数, テーブル, キーの初期値 do
ループの1回目は次のように値が代入されます。
key, value = テーブルとインデクスを受け取りキーとバリューを返す関数(テーブル, キーの初期値)
2回目以降は関数の第2引数に前回のキーの値が入ります。こうして順にキーとバリューを取り出していきます。
pairs関数は、next関数、引数で与えられたテーブル、nilの3つを返す関数になっていて、上記の条件を満たしています。
上記で定義したcycle関数は、next関数を返す代わりにキーが終端に来たら最初に戻る関数を返すことで、循環させています。
i = i or 0
というのは、iがnilならば、orの右辺を評価した値が式の値になります。つまりiがnilなら、0が、nil以外ならその値が、iに代入されます。
i = x[i + 1] and i + 1 or 1
これは、x[i + 1]の値がnilでなければi + 1が、nilならば1が式の値になり、iに代入されます。
つまりC/C++の三項演算子に似た効果がえられます。
トリッキーに見えますが「Programming in Lua プログラミング言語Lua公式解説書」にも書かれている、らしいです(わたし未読)。論理演算子の遅延評価を使ったテクニック。
これで循環する文字列の並びができました。ただこれだけだとテーブルの文字列が回るだけです。
-- 延々と「空文字列、空文字列、Fizz、空文字列、空文字列、Fizz、…」を表示しつづける for k, v in cycle({ "", "", "Fizz" }) do print(v) end
これを取り出して扱えるためにcoroutineを使います。fizz、buzzがそれにあたります。上記では関数の定義とコルーチンの生成を同時にしているのでわかりにくくなっていますが、わけて書くとこんな感じです。
function fizz_func() for k, v in cycle({ "", "", "Fizz" }) do coroutine.yield(v) end end fizz = coroutine.create(fizz_func)
さて、これでループ一回ごとに処理が戻ってくるようになりました。あとはfizzとbuzzと数列を組み合わせて表示するだけ。
ただここがまたちょっと不格好。もう一段階、どうにかしたいところ。