Programming in Lua のデータ永続化に関する章の中に、「Lua のテーブルリテラルの形でデータファイルを記述する」話が出てくる。つまり、データファイルの記述形式を次のようと、簡単に読込が可能になるという話である。
Entry{ author = "Donald E. Knuth", title = "Literate Programming", publisher = "CSLI", year = 1992 }
実はこれはそのままLuaのプログラムになっていて({ author = ... }
が表すテーブルを引数にして関数 Entry()
を呼び出している)、適切な関数 Entry()
を用意した上でこのファイル(プログラムソース)を dofile()
を読み込めば、その複雑な構造のデータが(Lua インタプリタの構文解析器で)解釈されて処理されることになる。同じ文献には次の例―著者名の(HashSet 方式の)集合を作る―が載っている。
local authors = {} -- a set to collect authors function Entry (b) authors[b.author] = true end dofile("data")
ところで、上のコードは Entry()
をグローバルに定義している。これ自体がメインのプログラムであれば問題ないが、これが例えばライブラリ的なパッケージの中の記述、例えば、
bibmanage = {} -- パッケージ function bibmanage.author_set(bibfile) local authors = {} function Entry (b) authors[b.author] = true end dofile(bibfile) return authors end -- 使用例 local set = bibmanage.author_set("bib.dat") for key, _ in pairs(set) do print(key) end print(Entry) -- → "function: XXXXXXXX" となる
のようなものだと、Entry()
はグローバルでなく、「この関数の実行中だけ」で可視になってほしい。どうすればこれを実現できるか。
Entry
をローカル(local)にするのは(例によって)失敗する、つまり、bibfile の「コード」中で Entry()
が不可視になってしまいエラーになる。Lua の local の意味するところは「この関数の定義本体中でのみ可視」(レキシカルスコープ)で、dofile()
で読まれるコードは「別の場所」にあるので、そこには含まれないのである。
では、どうすればよいか。ローカル定義は使えない(bibfile のファイル中に直に書かない限り「同じ場所」にはない)のであるが、幸い、Lua では関数のもつ「グローバル環境」を変更することができるので、dofile()
が表す関数の「グローバル環境」に Entry
を置くことで、目的が達成できる。
bibmanage = {} -- パッケージ function bibmanage.Entry(b) bibmanage._authors[b.author] = true end function bibmanage.author_set(bibfile) bibmanage._authors = {} local fun = loadfile(bibfile) local newenv = { Entry = bibmanage.Entry } setmetatable(newenv, { __index = getfenv(fun) }) setfenv(fun, newenv); fun() return bibmanage._authors end -- 使用例 local set = bibmanage.author_set("bib.dat") for key, _ in pairs(set) do print(key) end print(Entry) -- → "nil" となる
上の実装中で、fun()
(bibfile から読んだコードからなる関数)のグローバル環境を単に newenv
とするのでなく、newenv
に元のグローバル環境(これは実際には _G
である)を連鎖させているが、これは、fun()
の中で通常の Lua のライブラリ関数を可視にするためである。