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 のライブラリ関数を可視にするためである。