local修飾子の働き(PUC-Lua)
はじめに
こんにちは、びしょ〜じょです。今日はPUC-Luaにおいて、local
修飾子がどういう働きを持っているのかを見ていきたいと思います。
PUC-Luaとは、リオデジャネイロカトレア大学が開発している、いわゆる本家Lua処理系。正式名称は"Lua"で良いと思うが、Luaというプログラム言語のみならずその言語処理系について言及している、ということを強調するためにもPUC-Luaとした。Luaの処理系といえば他にもLaJITなんかがあるが、LuaJITの仕組みは本記事の限りではない。ということで今回はPUC-Luaの挙動を見ていく。 Luaプログラムと、生成されるLua VMの命令列を比較して何がどうなってるのかを見てみましょう。PUC-Luaといえば軽量さが売りであるため、様々なプロダクトに埋め込まれていたりする。例えばLuaTeX、Wireshark、FreeBSD etc. であり、バイトコードコンパイラおよびVMが大改造されていなければ本記事の内容が有効であろう。
レッツLua!!!! 今回はLua 5.3.4を使用する。
併読したい
プログラム言語Raviの開発陣がLuaのVM命令についてまとめたものがあるんで気になったら一緒に読んでみてください。VM命令に関する公式ドキュメントは無し。ソースコードにコメントがある程度。
local
とグローバル
"Luaはlocal
修飾子をつけないで変数宣言するとグローバル変数になってしまう"という話をよく聞くかもしれない。だいたいあってるが、もう少し詳しく見てみよう。
さて次のようなプログラムを見てみよう。
x = 42
これがどういった命令列になるのか見てみよう。
# "-l"で命令列を表示、"-l -l"でデバッグ情報含め全部表示
$ luac -l -l tekitou.lua -o /dev/null
main <tekitou.lua:0,0> (2 instructions at 0x1d33ca0)
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
1 [1] SETTABUP 0 -1 -2 ; _ENV "x" 42
2 [1] RETURN 0 1
constants (2) for 0x1d33ca0:
1 "x"
2 42
locals (0) for 0x1d33ca0:
upvalues (1) for 0x1d33ca0:
0 _ENV 1 0
SETTABUP
命令で_ENV
というtableにキーx
で42
を突っ込んでいる。
ではlocal
をつけると?
local x = 42
$ luac -l -l -o /dev/null tekitou.lua
main <tekitou.lua:0,0> (2 instructions at 0x1c6cca0)
0+ params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
1 [1] LOADK 0 -1 ; 42
2 [1] RETURN 0 1
constants (1) for 0x1c6cca0:
1 42
locals (1) for 0x1c6cca0:
0 x 2 3
upvalues (1) for 0x1c6cca0:
0 _ENV 1 0
LOADK
命令になっとる! これは定数をレジスターにロードする命令です。なるほどたしかにローカルな感じあるな。スコープを作ってローカル感を味わいましょう。
local x = 4
do
local x = 5
end
print(x) -- prints `4`
$ lua -o /dev/null -l -l scoped.lua
main <scoped.lua:0,0> (6 instructions at 0x1f4cca0)
0+ params, 3 slots, 1 upvalue, 2 locals, 3 constants, 0 functions
1 [1] LOADK 0 -1 ; 4
2 [4] LOADK 1 -2 ; 5
3 [7] GETTABUP 1 0 -3 ; _ENV "print"
4 [7] MOVE 2 0
5 [7] CALL 1 2 1
6 [7] RETURN 0 1
constants (3) for 0x1f4cca0:
1 4
2 5
3 "print"
locals (2) for 0x1f4cca0:
0 x 2 7
1 x 3 3
upvalues (1) for 0x1f4cca0:
0 _ENV 1 0
do end
でのx
の定義はレジスター1に5
を入れてますが、ブロックを抜けたあとのprint
関数もレジスター1にロードしています。do end
ブロック内のローカル定義を無視しています! ローカルみがありますね。print
に渡されるx
は、do end
の上にある方が、つまりレジスター0につっこまれてる値がMOVE
命令経由で渡っています。
なるほどね〜ではこれはどうかな
local x = 4
local x = 5
print(x) -- prints `5`
$ luaco scoped.lua
main <scoped.lua:0,0> (6 instructions at 0x234bca0)
0+ params, 4 slots, 1 upvalue, 2 locals, 3 constants, 0 functions
1 [1] LOADK 0 -1 ; 4
2 [2] LOADK 1 -2 ; 5
3 [4] GETTABUP 2 0 -3 ; _ENV "print"
4 [4] MOVE 3 1
5 [4] CALL 2 2 1
6 [4] RETURN 0 1
constants (3) for 0x234bca0:
1 4
2 5
3 "print"
locals (2) for 0x234bca0:
0 x 2 7
1 x 3 7
upvalues (1) for 0x234bca0:
0 _ENV 1 0
あーそうなる? 1行目のx
の宣言でレジスター0に値を突っ込んでいますが、2行目はレジスター0にではなく新たにレジスター1に値を突っ込んどります。1つ前の例とは異なり、レジスター2にprint
関数をロードしているのでレジスター1の値は無視されておらず、どうやらlocal
修飾子は新たなレジスターを確保して値を突っ込む働きをしてくれるようだ。
モジュールとローカル
Luaはモジュール機能を持っている。詳細は割愛。Luaファイルをモジュールとして読み込むというシーンを考えたい。 たとえばこんなファイル。
x = 42
require("mod")
print(x) -- prints `42`
mod.luaでx
をグローバルに定義し、mod.luaを読み込むとmain.luaでもx
が呼べる。なるほど。残念なことにLuaはOCamlのmliファイルのような、モジュールが提供しているものの情報を見せる機能がない。したがってあまりモジュールでグローバル定義したくない。
ではどうする? require
関数の戻り値に鍵がある。require
の戻り値は、渡されるLuaファイルを1チャンクとして実行したときの戻り値となる。
つまり??? 手を動かしてみよう。
return 42
local x = require("modx")
print(x) -- prints `42`
あーなるほどそういうことね完全に理解した モジュールが複数の関数や値を提供したいときは、そうだねtableだね。
local x = 3
local y = 4
local f = function()
return x
end
return {
x = x,
y = y,
f = f
}
local x = 2
local m = require("modsomething")
print(m.f()) -- prints `3`
なるほど。 当然ながらmodsomething.luaで定義した関数f
が参照する環境は当然modsomething.luaの中です。
追記 2018/05/21
そういえばLuaはmultiple value(多値)も使えるんだった。 モジュールも多値を返せます。
これらについてはこちらでもう少し述べているので読んでみてください。
おわりに
"local
をつけないとグローバル定義になっちゃうなんて…"なんて言ってLuaを敬遠するのはもったいない!
これからどんどん組み込みスクリプト言語として流行っていく…気がする…