マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

Lua のテーブルリテラルによるデータファイル形式

Programming in Luaデータ永続化に関する章の中に、「Lua のテーブルリテラルの形でデータファイルを記述する」話が出てくる。つまり、データファイルの記述形式を次のようと、簡単に読込が可能になるという話である。

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