はじめに

こんにちは、びしょ〜じょです。今日は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修飾子をつけないで変数宣言するとグローバル変数になってしまう"という話をよく聞くかもしれない。だいたいあってるが、もう少し詳しく見てみよう。

さて次のようなプログラムを見てみよう。

tekitou.lua
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にキーx42を突っ込んでいる。

ではlocalをつけると?

tekitou.lua
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命令になっとる! これは定数をレジスターにロードする命令です。なるほどたしかにローカルな感じあるな。スコープを作ってローカル感を味わいましょう。

scoped.lua
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命令経由で渡っています。 なるほどね〜ではこれはどうかな

scoped2.lua
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ファイルをモジュールとして読み込むというシーンを考えたい。 たとえばこんなファイル。

mod.lua
x = 42
main.lua
require("mod")
print(x) -- prints `42`

mod.luaでxをグローバルに定義し、mod.luaを読み込むとmain.luaでもxが呼べる。なるほど。残念なことにLuaはOCamlのmliファイルのような、モジュールが提供しているものの情報を見せる機能がない。したがってあまりモジュールでグローバル定義したくない。 ではどうする? require関数の戻り値に鍵がある。requireの戻り値は、渡されるLuaファイルを1チャンクとして実行したときの戻り値となる。 つまり??? 手を動かしてみよう。

modx.lua
return 42
main.lua
local x = require("modx")
print(x) -- prints `42`

あーなるほどそういうことね完全に理解した モジュールが複数の関数や値を提供したいときは、そうだねtableだね。

modsomething.lua
local x = 3
local y = 4

local f = function()
  return x
end

return {
  x = x,
  y = y,
  f = f
}
main.lua
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を敬遠するのはもったいない! これからどんどん組み込みスクリプト言語として流行っていく…気がする…