エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Fizz Buzz by Lua part2

で、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

というのは、inilならば、orの右辺を評価した値が式の値になります。つまりinilなら、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を使います。fizzbuzzがそれにあたります。上記では関数の定義とコルーチンの生成を同時にしているのでわかりにくくなっていますが、わけて書くとこんな感じです。

function fizz_func()
  for k, v in cycle({ "", "", "Fizz" }) do
    coroutine.yield(v)
  end
end

fizz = coroutine.create(fizz_func)


さて、これでループ一回ごとに処理が戻ってくるようになりました。あとはfizzbuzzと数列を組み合わせて表示するだけ。
ただここがまたちょっと不格好。もう一段階、どうにかしたいところ。