時代遅れひとりFizzBuzz祭り、今回はLua。前回のObjective Camlとの繋がりは、最近趣味で触りはじめた言語だという点。少なくとも業務都合で勉強することになった訳ではない。
Lua自体は2008年末ぐらいから注目していた。一応組込み関連の仕事をしているので、ゲーム開発で採用されているという話を聞いて「数年後に組込み機器でも採用例が出るかも」と考えていた訳だ。そしたら2009年10月にヤマハのルーターRTX1200のファームウェアにLuaスクリプト機能が組み込まれたとのアナウンスがあり、予想より早く採用例が出たことに驚いたのだった。
Luaはコンパクトなので、FizzBuzzで基本機能を一通り触ってみようと思う。もっとも一回のエントリでは厳しいので、前編と後編に分けることにする。
LuaについてはOCamlよりも経験値が低いので、まずオーソドックスなFizzBuzzを書いてみた。ちなみにLua 5.1.4だ。
function isnumber (n) if type(n) == "number" then return true elseif type(n) == "string" then if tostring(n) == nil then return false else return true end else return false end end function fizzbuzz (n) assert(isnumber(n) and n > 0, n .. " is not number or less than 1") if n%15 == 0 then return "FizzBuzz" elseif n%3 == 0 then return "Fizz" elseif n%5 == 0 then return "Buzz" else return n end end for i = 1, 100, 1 do print(fizzbuzz(i)) end
念の為、数値として扱える値かチェックするisnumberを定義して、assertを仕掛けている。動的型付けの意味が薄れている気もするが、気にしないことにする。
Luaの演算子や制御構造はシンプルで、C言語と比べてもswitchのような多岐分岐やループ制御のcontinueが無かったりする。それでも回避方法があるので、実用上は問題ない。多岐分岐はif - elseif - elseのチェーンで書けるし、continueはVBScriptのようにループの中に1回しか実行されないダミーのループを用意することでシミュレートできる。
ちょっと驚いたのが、論理演算子のandとorでC言語の条件演算子(三項演算子)を真似る書き方。
function isnumber (n) if type(n) == "number" then return true elseif type(n) == "string" then if tostring(n) == nil then return false else return true end else return false end end function fizzbuzz (n) assert(isnumber(n) and n > 0, n .. " is not number or less than 1") local val = n%3 == 0 and "Fizz" or "" if n%5 == 0 then val = val .. "Buzz" end return val == "" and n or val end for i = 1, 100 do print(fizzbuzz(i)) end
若干注意が必要な書き方だが、『Programming in Lua プログラミング言語Lua公式解説書』に載っているぐらいなので、Luaでは多分一般的な書き方なのだろう。
Luaにはデータ構造としてテーブルが用意されている。というかテーブルが唯一のデータ構造らしい。テーブルは配列や連想配列として使えるし、オブジェクトにもなる。何というかJavaScriptのオブジェクトと似ているが、当然ながらプロトタイプチェーンは無い。
function isnumber (n) if type(n) == "number" then return true elseif type(n) == "string" then if tostring(n) == nil then return false else return true end else return false end end function fizzbuzz (n) assert(isnumber(n) and n > 0, n .. " is not number or less than 1") local ref_tbl = {n, "Fizz", "Buzz", "FizzBuzz"} local fizz = n%3 == 0 and 1 or 0 local buzz = n%5 == 0 and 2 or 0 return ref_tbl[fizz + buzz + 1] end for i = 1, 100 do print(fizzbuzz(i)) end
テーブルを配列として使ってみた。Luaの流儀では添字は1から始まる。0から始まるようにも書けるが、1から始まるという前提で実装されている機能もあるので、自重しておいた方が無難だろう。
テーブルから話は逸れるが、Luaの関数はファーストクラスのオブジェクトだ。使い勝手は、これまたJavaScriptの関数に近いものがある。
function isnumber (n) if type(n) == "number" then return true elseif type(n) == "string" then if tostring(n) == nil then return false else return true end else return false end end function fizzbuzz (n) assert(isnumber(n) and n > 0, n .. " is not number or less than 1") local ref_tbl = {n, "Fizz", "Buzz", "FizzBuzz"} local function fizz () if n%3 == 0 then return 1 else return 0 end end local function buzz () if n%5 == 0 then return 2 else return 0 end end return ref_tbl[fizz() + buzz() + 1] end for i = 1, 100 do print(fizzbuzz(i)) end
関数fizzbuzzの中でローカルなクロージャを作ってみた。これも例えば、
local function fizz () if n%3 == 0 then return 1 else return 0 end end
の部分は次のようにも書ける。
local fizz = function () if n%3 == 0 then return 1 else return 0 end end
ますますJavaScriptと似ている気がしてきた。
テーブルを触ったところで、次はジェネリックforを弄くってみることにした。まずは普通にジェネリックforを使ってみた。
function isnumber (n) if type(n) == "number" then return true elseif type(n) == "string" then if tostring(n) == nil then return false else return true end else return false end end function fizzbuzz (n) assert(isnumber(n) and n > 0, n .. " is not number or less than 1") local ref_tbl = {n, "Fizz", "Buzz", "FizzBuzz"} local fizz = n%3 == 0 and 1 or 0 local buzz = n%5 == 0 and 2 or 0 return ref_tbl[fizz + buzz + 1] end function make_table (n, fn) assert(isnumber(n) and type(fn) == "function") local tbl = {} for i = 1, n do tbl[i] = fn(i) end return tbl end for i, v in ipairs(make_table(100, fizzbuzz)) do print(v) end
イテレータipairsを使いたいが為に、関数make_tableを定義している。高階関数も簡単に書ける所は、やはりJavaScriptに似ている。
また話は逸れるが、Luaでは関数の末尾呼び出しが最適化される。なので末尾再帰も最適化されるはずだ。
function isnumber (n) if type(n) == "number" then return true elseif type(n) == "string" then if tostring(n) == nil then return false else return true end else return false end end function fizzbuzz (n) assert(isnumber(n) and n > 0, n .. " is not number or less than 1") local ref_tbl = {n, "Fizz", "Buzz", "FizzBuzz"} local fizz = n%3 == 0 and 1 or 0 local buzz = n%5 == 0 and 2 or 0 return ref_tbl[fizz + buzz + 1] end function make_table (n, fn) assert(isnumber(n) and type(fn) == "function") local function make_table_iter (n, tbl) if n <= 0 then return tbl else tbl[n] = fn(n) return make_table_iter(n-1, tbl) end end return make_table_iter(n, {}) end for i, v in ipairs(make_table(100, fizzbuzz)) do print(v) end
make_tableを末尾再帰のスタイルに書き直してみた。最適化されてスタックオーバーフローにはならないはずだ。この辺りはJavaScriptとは違う*1。
今回はここまで。普通にジェネリックforを使ってみたので、次回はジェネリックforで使用するイテレータを自作してみたいと思う。
*1:正確に書くと、ECMAScript 3.0の仕様には「末尾再帰の最適化」は含まれていない。で、末尾再帰を最適化するJavaScriptの処理系はごく僅かだ。私自身、Rhinoのインタープリタモードしか知らない。