Luaのモジュール徹底解説（Lua 5.1〜5.3対応）

この文書では、Luaのモジュールの仕組みと、チャンクについて解説する。

モジュールを書く側の話に関しては、この文書ではLuaで書く場合を主に解説し、Cでの書き方は解説しない。

Luaのモジュールシステムは Lua 5.1 と Lua 5.2 以降で色々変化があった。この記事ではその違いにも留意して解説する。なお、 LuaJIT は基本的に Lua 5.1 と互換なので、 LuaJIT の場合は Lua 5.1 向けの説明を読んでほしい。

なお、ここで解説する仕様とベストプラクティスはLuaの標準的な話であって、他のソフトウェアに組み込まれたLuaの場合は組み込む側の都合によって色々変更されている可能性がある。

# `require` の仕組みとカスタマイズ方法

まず、 `require` を呼び出した時に何が起こるかを確認しておこう。読み込みたいモジュール名（`require` の引数）は `modname` とする。

1. 当該モジュールが読み込み済みかを検査する。
    1. `package.loaded` テーブルの当該キー（つまり `package.loaded[modname]`）に値があるかを検査する。あれば、その値を返す。
2. 当該モジュールが読み込み済みでない場合、**ローダー**と呼ばれる関数を以下の手順で探す（この手順は `package.searchers` [^searchers]テーブルをいじることでカスタマイズできる。以下の手順は、 `package.searchers` がカスタマイズされていないと仮定した場合のデフォルト値）
    1. `package.preload[modname]` を見る。ここに値が入っていれば、それがローダーである。
    2. `package.path` に基づいて Lua モジュールを探す。
    3. `package.cpath` に基づいて C モジュールを探す。
    4. オールインワンローダーを探す。（詳細略）
3. ローダーを、「モジュールの名前」を引数として呼び出す。（ローダーがファイルから見つかった場合は、引数としてモジュール名の他にファイル名が渡される。）
4. 次回同じモジュールを `require` した場合のために、 `package.loaded[modname]` を適宜設定する。
    1. ローダーが `nil` でない値を返した場合、その値を `package.loaded[modname]` に設定する。
    2. ローダーが `nil` を返した場合（ローダーが値を返さなかった場合も含む）、
        1. ローダー自身が `package.loaded[modname]` を設定していれば、それで良い。
        2. そうでなければ、 `package.loaded[modname]` に `true` を設定する。
5. 最終的な `package.loaded[modname]` の値を返す。

[^searchers]: Lua 5.1においては、 `package.searchers` ではなく `package.loaders` という名前だった。

ここの動作はLua 5.1からLua 5.3までそんなに変わっていない。

というわけで、 `require` の挙動をカスタマイズしたい場合（組み込みモジュールを追加したい、モジュールの探索場所を変えたい、等）には、大雑把に

1. `package.loaded` をいじる
2. `package.preload` をいじる
3. `package.path` や `package.cpath` をいじる（環境変数から設定する場合は `LUA_PATH` や `LUA_CPATH` をいじる）
4. `package.searchers` をいじる

という4つの手段があることになる。以下、どういう場合にどの方法を使うと良いか、見ていこう。

Luaを組み込んだ実行環境が新たな組み込みモジュールを増やしたい（例えば、LuaFileSystem `lfs` を内蔵したい）場合は、ローダー（LuaFileSystemの場合は、Cで書かれた `luaopen_lfs`）を呼び出して `package.loaded` にその返り値を設定するのが適切である[^luajittex-lfs]。Luaコードからそのモジュールを使いたい場合は、通常通り `require` を使うことができる。

[^luajittex-lfs]: 余談だが、LuajitTeXはこの部分に不備があり、LuaFileSystemを組み込んでいる（グローバル変数 `lfs` が存在する）にも関わらず `require "lfs"` が動作しない。対処法は、 `require "lfs"` を呼ぶ前に `if lfs and package.loaded["lfs"] then package.loaded["lfs"] = lfs end` を実行しておくことである。

（ちなみに、Lua標準のモジュール（`string` とか `table` とか `os` とか）も `require` で取得できる：`local string = require "string"` など）

ローダーを事前に実行するのではなく、 `require` が呼ばれたタイミングで実行したい場合は、 `package.preload` にローダーを設定しておくと良い。

LuaRocksのように、パッケージマネージャーが管理するモジュール置き場があってそこを使えるようにしたいという場合は、 `package.path`/`package.cpath` （あるいは環境変数 `LUA_PATH`/`LUA_CPATH`）をいじるのが適当である。

Lua以外のエコシステム（例：TeX）の仕組みに基づいてモジュールを読み込みたい、という場合は `package.searchers` をいじるのが適当である。

# モジュールを書く側のベストプラクティス

ローダーに期待される動作は、

1. モジュールに相当する値（典型的にはテーブル）を返す
2. モジュールに相当する値（典型的にはテーブル）を `package.loaded[modname]` に設定する

のいずれかである（両方行っても良い）。

モジュールと同名のグローバル変数を設定するかどうかは `require` の動作には影響しないし、 Lua 5.2 以降は同名のグローバル変数は設定しないのが一般的である。

Luaでモジュールを書く場合は、「値を返す」のが一般的である。つまり、

```lua:foo.lua
local foo = {}
foo.greet = function()
  print("Hello world!")
end
return foo
```

や

```lua:foo.lua
local function greet()
  print("Hello world!")
end
return {
  greet = greet,
}
```

と書く。この書き方は Lua 5.1〜5.3 いずれでも動作する。

Lua 5.1 時代は Lua でモジュールを書く際に `module` という関数を使うのが一般的だったが、これはもはや黒歴史であり、忘れて良い。

モジュールを定義する際にうっかり `local` を書き忘れてしまうと、不用意にグローバル変数を定義してしまうことになり、よろしくない。対処方法は後述する。

自身のモジュール名を取得したい場合は、チャンクの引数（後述）が利用できる。例：

```lua:foo.lua
local modname = ... -- チャンクの引数
local function greet()
  print("Hello from " .. modname)
end
return {
  greet = greet,
}
```

# モジュールを使う側のベストプラクティス

`require` はモジュールを表す値（典型的にはテーブル）を返すので、それをローカル変数で受けて利用すれば良い。

例えば、 `foo` という名前のモジュールを使う際は、

```lua
local foo = require "foo"
foo.greet() -- モジュールを使う
```

とする。この書き方は Lua 5.1〜5.3 のいずれでも通用する。

Lua 5.1 時代（〜2011年）は、慣習として、ローダー自身がグローバル変数を設定することが多かったので

```lua:古い書き方
require "foo"
foo.greet()
```

という書き方もできたが、 Lua 5.2 時代（2012年〜）からはローダーがグローバル変数を設定することは少なくなった。そのため、 Lua 5.1 向けのLuaコードであっても、最新版のライブラリーを使う場合は後者の書き方（グローバル変数が設定されていることを期待する）はできない。

例：LuaFileSystemのローダーは、以前は `lfs` という名前のグローバル変数を設定していたが、今はそうではない（[当該コミット](https://github.com/keplerproject/luafilesystem/commit/8e7217e74fbe5da0a9c1fee03d191b5a0266cedd)）。筆者が作っている[luaexif](https://github.com/minoki/luaexif)も、Lua 5.2対応に合わせてグローバル変数を設定しないように[仕様変更](https://github.com/minoki/luaexif/commit/62fa8a52ec1eae2df02a8ecdbb27bcb2f2abf43b)した。

なお、Lua標準のインタープリター `lua` の `-l` オプションで読み込まれたモジュールに関しては、 Lua 5.2 以降でも（Luaインタープリター側の処理によって）同名のグローバル変数が設定される。

# チャンク：ソースコードと関数の関係

Luaでファイルを（モジュールとしてではなく、単に）実行するには `dofile` 関数を使う。この「ファイルを実行する」という処理は、

1. 当該ファイルを読み取り、バイトコードへコンパイルする
2. コンパイルしたバイトコードを実行する

の2つの処理から成ると考えられる。

Luaにおいてはこの2つ（コンパイルと実行）を別々に行うことができる。そして、「コンパイルしてできるもの」は普通の関数である。（この「関数」は、バイトコードと環境 `_ENV`[^env] の組である。環境 `_ENV` の初期値は `load` の引数で与えることができる）

[^env]: 関数の環境が `_ENV` と呼ばれる上位値になったのは Lua 5.2 以降の話で、 Lua 5.1 時代は同様のものが `getfenv`/`setfenv` で取得・設定できた。

ファイルに対してコンパイルのみを行う関数は `loadfile` であり、`dofile` を `loadfile` を使って実装するなら次のようになる：

```lua
function dofile(filename)
  local chunk, err = loadfile(filename)
  if chunk ~= nil then -- 正常に読み込めた：
    return chunk() -- 実行
  else -- エラー：
    return nil, err
  end
end
```

ソースコードをファイル以外（例：文字列リテラル）から読み込むことも当然可能である。その場合は `loadfile` の代わりに `load` （Lua 5.2以降）または `loadstring` （Lua 5.1）を使う。

他の言語での `eval` 関数みたいな処理（文字列をその場でコンパイルし、実行する）は、Luaでは

```lua
assert(load("local a = 1 + 1; print(a)"))()
-- Lua 5.1の場合は
-- assert(loadstring("local a = 1 + 1; print(a)"))()
```

となる。

Luaのソースコードのひとまとまり（ファイルや文字列）、またはそれらをコンパイルしてできた関数は**チャンク**と呼ばれる。トップレベルの `local` で宣言した変数のスコープは一つのチャンクで完結するため、異なるチャンクの間では共有されない。

例：Luaの対話環境（REPL）においては1行[^repl-line]が1つのチャンクに相当する。そのため、先の行で定義したローカル変数は後の行で参照できない。

[^repl-line]: 入力が1行で完結しない場合は、複数行。

```lua:Luaの対話環境
> local a = 123 -- 先の行で定義したローカル変数は
> print(a) -- 後の行で参照できない
nil
```

例：LuaTeXの `\directlua` の中で定義したローカル変数は後の `\directlua` から参照できない。1つの `\directlua` 呼び出しが1つのチャンクに相当するからである。

```tex
\directlua{local a = 123; tex.print(tostring(a))} % --> 123
\directlua{tex.print(tostring(a))} % --> nil
\bye
```

## チャンクの引数と返り値

Luaのチャンクが普通の関数だということは、チャンクも引数と返り値を持てるということである。

チャンクの返り値は「モジュールを書く側のベストプラクティス」ですでに登場している。`return` を書けば良い。

チャンクの引数は、可変長引数である。Luaで `function(...) ほにゃらら end` と書いた場合と同等である。

例：Lua標準のインタープリターでLuaファイルを実行した場合、チャンクの引数としてコマンドライン引数が渡される。

```lua:hoge.lua
local arg1, arg2, arg3 = ...
print(arg1, arg2, arg3)
local args_table = table.pack(...)
print("Number of arguments: ", #args_table)
```

```
$ lua hoge.lua foo bar
foo	bar	nil
Number of arguments: 	2
$ lua hoge.lua a b c d
a	b	c
Number of arguments: 	4
```

ただし、コマンドライン引数は `arg` という名前のグローバル変数としても渡されるので、普通にプログラムを書くときは `arg` を使うことが多いかもしれない。

例：`require` がLuaモジュールのローダーを呼び出す際には、モジュール名（とファイル名）が渡される。

```lua:foo.lua
local modname = ... -- チャンクの引数
local function greet()
  print("Hello from " .. modname)
end
return {
  greet = greet,
}
```

```lua:実行例
local foo = require "foo"
foo.greet() -- Hello from foo
```

## チャンクと関数

というわけで、

```lua
-- すごい処理
print("Hello world!")
```

という内容のチャンク（をコンパイルして得られる関数）は

```lua
function(...)
  -- すごい処理
  print("Hello world!")
end
```

という関数とだいたい等価である。

細かいことを言うと、Lua 5.2 でグローバル変数（`local` で束縛されていない変数、と言った方が適切かもしれない）へのアクセス方法が変わった関係で、 Lua 5.2 以降での相当する擬似コードは

```lua
(function()
  local _ENV = <グローバル変数に相当するテーブル>
  return function(...)
    -- すごい処理
    print("Hello world!") -- この print は _ENV.print と等価
  end
end)()
```

となる。

# おまけ：不用意にグローバル変数を触っていないかチェックする

モジュールを書く際にうっかり `local` を書き忘れると、グローバル変数へのアクセスが発生してグローバル環境を汚染してしまう。そのため、意図しないグローバル変数へのアクセスが発生していないか検査する方法があると良い。

Lua 5.2 では、グローバル変数（というか、束縛されていない変数）へのアクセスは `_ENV` という名前の変数を介するようになったので、 `_ENV` に `nil` でも代入しておけば不用意にグローバル環境を汚染することは避けられる。

```lua:bar.lua
local _G = _G -- グローバル変数が入ったテーブル
_ENV = nil
local bar = {}
bar.greet = function()
  _G.print("Hello world!") -- OK
  print("Goodbye world!") -- エラー！ attempt to index a nil value (upvalue '_ENV')
end
return bar
```

グローバル変数への意図しないアクセスを実行せずに検出したい、という場合は、 `luac -l` を使うと良い（`luac` というのは与えられたチャンクをバイトコードにコンパイルするコマンドであり、 `-l` オプションはコンパイル後のバイトコードをテキスト形式で印字してくれる）。

```
$ luac -l bar.lua

main <bar.lua:0,0> (8 instructions at 0x7fec42500080)
0+ params, 3 slots, 1 upvalue, 2 locals, 2 constants, 1 function
	1	[1]	GETTABUP 	0 0 -1	; _ENV "_G"
	2	[2]	LOADNIL  	1 0
	3	[2]	SETUPVAL 	1 0	; _ENV
	4	[3]	NEWTABLE 	1 0 0
	5	[7]	CLOSURE  	2 0	; 0x7fec42500230
	6	[7]	SETTABLE 	1 -2 2	; "greet" -
	7	[8]	RETURN   	1 2
	8	[8]	RETURN   	0 1

function <bar.lua:4,7> (7 instructions at 0x7fec42500230)
0 params, 2 slots, 2 upvalues, 0 locals, 3 constants, 0 functions
	1	[5]	GETTABUP 	0 0 -1	; _G "print"
	2	[5]	LOADK    	1 -2	; "Hello world!"
	3	[5]	CALL     	0 2 1
	4	[6]	GETTABUP 	0 1 -1	; _ENV "print"
	5	[6]	LOADK    	1 -3	; "Goodbye world!"
	6	[6]	CALL     	0 2 1
	7	[7]	RETURN   	0 1
```

グローバル変数へのアクセスは `_ENV` に対する `GETTABUP` 命令および `SETTABUP` 命令として印字される。後は、アクセスしている変数名が意図したものかどうかをチェックすれば良い。目視で検査するなら `luac -l bar.lua | grep _ENV` を実行すると良いだろう。

筆者がLuaで書いている [cluttex](https://github.com/minoki/cluttex) というプログラムでは、 `luac` を使ってグローバル変数へのアクセスを検査するスクリプトを用意している。コード→ [checkglobal.lua](https://github.com/minoki/cluttex/blob/master/checkglobal.lua)