で、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
と数列を組み合わせて表示するだけ。
ただここがまたちょっと不格好。もう一段階、どうにかしたいところ。