こんにちは、びしょ〜じょです。

昨日はやっとアイカツ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と何か関係があるのではないか、という考えをするのが人間です。 実はだいぶあります。

ところで、ここで関数のおさらいです。以下の関数fg全く同じ意味を持ちます。

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.outcompiled_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

簡単に説明すると、

  1. _ENVからprintをスタックに積む
  2. "hello"をスタックに積む
  3. 3をスタックに積む
  4. 関数呼び出し

といった感じでしょうか。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_はまず_ENVf_を登録して、次に値を与えています。このため、定義時に_ENV.f_が存在はするため、戻り値に(_ENV.)f_を返せる。 一方g_g_自身の定義時に_ENVg_がないため、戻り値は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で引数のfgを取得しているようだ。 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について話します。よろしくお願いします。