Luaでは関数外でもreturnが書ける、これな〜んでだ?
こんにちは、びしょ〜じょです。
昨日はやっとアイカツ2016年第4弾をやりました。 「馬鹿野郎お前俺は1万円は使うぞお前」という気持ちで行ったんですが、2hほど筐体前で気持ちよくなってたけど結局3、4000円程度しか使えませんでした。 実際文章をちゃんと読んで聞いてしてると1プレイが結構長いんですね、3ヶ月ぶりだったので忘れてました。 結局1000円札5枚を100円玉50枚にしたら、全部使うことなく閉店時間になり小銭入れがパンパンのまま帰る羽目になりました。
データカードダス アイカツスターズ! は5月19日から! ということでまたやりに行こうと思います。CPのスクールドレスは絶対にそろえたいなぁ、あときいちゃんが書いてあるやつ!
1. Q. Luaでは関数外でもreturnが書ける、これな〜んで?
実はLuaでは関数外でもretun
書けるし構文的には何の問題もないんですねぇ…。
Luaの予約語であるreturn
は、CとかJavaなどと同じで、関数の戻り値を書くわけです(多値という違いはありますが)。
-- foo.lua
print "Hello"
return 5 --!!!
for i = 1, 10 do
......
2. A. require
の戻り値に渡すため
実はなんてことない話で、require('foo')
したときの戻り値にreturn 5
が渡されるというだけです。
luaファイルは全てrequire([[filename]])
することでモジュールとして呼び出すことができ、filename.lua
の戻り値、つまりreturn ...
がrequire([[filename]])
の戻り値になります。
filename.lua
に戻り値がなければrequire([[filename]])
はtrue
(または呼び出し時に失敗すればfalse
)を返します。
3. 関数との関係
さて、関数のreturn
と何か関係があるのではないか、という考えをするのが人間です。
実はだいぶあります。
ところで、ここで関数のおさらいです。以下の関数f
とg
は全く同じ意味を持ちます。
local f
f = function() end
local function g() end
また、次の関数f_
とg_
は違います。
-- f_ contains f_
local f_
f_ = function() return f_ or 1 end
-- g_ DOES NOT contains g_
local g_ = function() g_ or 1 end
3-1. string.dump
まず関係を示すために、ユーザー定義関数をバイナリ表現にしてくれるstring.dump
について。
言ったまんまですが、定義した関数を渡すと、実際にLua VM上で動くバイトコードに変換してくれます。 使いどころがよくわからない気もしますが、今が使い時です。
簡単な関数について考えてみます。
local function kantannna_kansuu()
print("hello", 3)
end
print(string.dump(kantannna_kansuu))
これを実行するとよくわからない文字列が出力されます。
うーん、ではこの文字列をファイルに吐き出してみましょう。
local function kantannna_kansuu()
print("hello", 3)
end
local f = assert(io.open("kantannna_kansuu.out", "wb"))
f:write(string.dump(kantannna_kansuu))
f:close()
じゃあlua
で実行してみるか。
$ lua kantannna_kansuu.out
hello 3
はわわ、なんだかよくわからなくなってきました……。
もう少し頑張ってください。次のようなファイルを作ります。
-- kantannna_kansuu.lua
print("hello", 3)
そうです、kantannna_kansuu()
の中身と同じです。これをコンパイルします。はい、Luaはコンパイラがあります。
$ luac -o compiled_kantannna_kansuu.out kantannna_kansuu.lua
kantannna_kansuu.lua
をLua VMのバイトコードにコンパイルし、compiled_kantannna_kansuu.out
というファイルに書き出しました。
ここで!! おもむろにバイナリエディタなどでkantannna_kansuu.out
とcompiled_kantannna_kansuu.out
を見比べてみてください。
実はこれ全く同じなんですね。
4. 関数の実態
luac
コマンドには便利なオプションがああります。luac -l luac.out
でLua VMの挙動がわかります。
これでkantannna_kansuu.out
を見てみます。
$ luac -l kantannna_kansuu.out
function <stdin:1,3> (5 instructions at 0x129ba30)
0 params, 3 slots, 1 upvalue, 0 locals, 3 constants, 0 functions
1 [2] GETTABUP 0 0 -1 ; _ENV "print"
2 [2] LOADK 1 -2 ; "hello"
3 [2] LOADK 2 -3 ; 3
4 [2] CALL 0 3 1
5 [3] RETURN 0 1
簡単に説明すると、
-
_ENV
からprint
をスタックに積む -
"hello"
をスタックに積む -
3
をスタックに積む - 関数呼び出し
といった感じでしょうか。VMについてはまだわかってないので適当ですが、雰囲気としては問題ないはず。
あれ、お前、元は関数だったはずじゃ……これではただの実行ファイルじゃん。
そうなんです、Luaにおける関数とは、まさに引数をとる環境と言えるわけですね〜、逆にファイルなど一つのLuaチャンクはただの関数なんですね。
これは以下の2関数について、
-- f_ contains f_
local f_
f_ = function() return f_ or 1 end
-- g_ DOES NOT contains g_
local g_ = function() g_ or 1 end
f_
はまず_ENV
にf_
を登録して、次に値を与えています。このため、定義時に_ENV.f_
が存在はするため、戻り値に(_ENV.
)f_
を返せる。
一方g_
はg_
自身の定義時に_ENV
にg_
がないため、戻り値はg_
(つまりnil
)ではなく1
になるんです。
「引数を取る環境」とか言っておいて引数のある関数について見てませんね。
local function compound(f, g)
return function(x) return g(f(x)) end
end
local filename = "compound.out"
local f = assert(io.open(filename, "wb"))
f:write(string.dump(compound))
f:close()
print(os.execute("luac -l " .. filename))
function <stdin:1,3> (3 instructions at 0x155ea10)
2 params, 3 slots, 0 upvalues, 2 locals, 0 constants, 1 function
1 [2] CLOSURE 2 0 ; 0x155eae0
2 [2] RETURN 2 2
3 [3] RETURN 0 1
function <stdin:2,2> (7 instructions at 0x155eae0)
1 param, 4 slots, 2 upvalues, 1 local, 0 constants, 0 functions
1 [2] GETUPVAL 1 0 ; g
2 [2] GETUPVAL 2 1 ; f
3 [2] MOVE 3 0
4 [2] CALL 2 2 0
5 [2] TAILCALL 1 0 0
6 [2] RETURN 1 0
7 [2] RETURN 0 1
true exit 0 # これは`os.execute()`の戻り値
ウオア〜なんか2つ(?)出た、冷静に見てみよう。
上の方の命令にCLOSURE
というのがあるので、クロージャを作ってるcompound()
がこれかな。
下が、compound()
が返している関数の中身のようなので、これを見てみますと、まずGETUPVAL
で引数のf
とg
を取得しているようだ。
MOVE
命令でx
を引っ張ってCALL
してるように見えるおわり。
5. おわりに
「だから何だ」という話ですが、クイズの答えとその解説というだけで、どうするかは考えてください。
あわせて読みたい
A No-Frills Introduction to Lua 5.1 VM instructions
Lua VMの命令の解説です。Lua 5.1に関するものですが、VM自体は5.3になってもあまり進化してない(気がする)ので、現在でも使える知識です。
この記事と関係があるかないかで言うと、今ボクが読んでます。
Tsukuba.pm #3で話すことになりました。 @VienosNotes さんはボクがPerl書けないことは知ってるはずですが声をかけてもらったので、堂々とLuaについて話します。よろしくお願いします。