This Project Has Not Released Any Files
Lua のモジュール管理機能は 5.1 版になって追加(拡張) されたものであるので、 オンラインで読める初版の 「Programming in Lua」 (Lua 5.0 版を対象とする) では触れられていない。
※ と思ったら、第 2 版でモジュールについて触れられている 15 章だけ Ierusalimschy さんのページ でオンライン公開されていた……。
ここでは、Lua のモジュール機能の概略と、 LuaTeX の luatexbase-modutil パッケージの機能との関係について解説する。
本節では、LuaTeX に依存しない Lua のモジュール機能について述べる。
(※ なお、luatexbase.require_module() を用いる場合は、 必ずグローバル変数への格納を行う必要がある。)
モジュール名は foo.bar のような複合名の場合もある。 この場合、対応する「グローバル変数」 foo.bar というのは、 グローバル変数の foo にあるテーブルの中のキー bar ということになる。
require("foo") は以下の手順を実行する。
3. の動作は、(i) と (ii) の何れの場合にも「foo モジュール」 を戻り値と package.loaded["foo"] の値に設定する。 もし、(i) と (ii) のどちらも行われないという例外的な場合では、 true をこの両者の値として用いる (だから foo モジュールはやはり読み込み済になる)。
require() はグローバル変数 foo については全く何も関知しないことに注意。 モジュールローダーがグローバルの foo にモジュールを格納するかはローダー次第である。 もしグローバルに格納したのであれば、 以下のようにモジュール foo を使用できる:
require "foo" foo.some_func()
しかし、格納しない(という慣習を用いる) のであれば、呼び出し側が適当な処置を行う必要がある:
foo = require "foo" foo.some_func()
モジュールは単なるテーブルなので、別の変数に代入する等の処理が自由に行える。
require "foo" local F = foo -- あるいは local F = require "foo" でも同じ F.some_func()
モジュールローダーの取得には、モジュールローダー検索関数を用いる。 モジュールローダー検索関数は複数あり、配列 package.loaders に格納されている。 従って、モジュールローダーの取得の手順は以下のようになる。
Lua の既定の検索関数は以下の通り。 括弧内はその関数の実行で参照される変数。
(※ ただし、LuaTeX では 2 番目以降のものが、 Kpathsea を利用して同等のファイル検索を行うものに置き換えられている。)
package.loaders[2] について補足しておく。 例えば package.path が次のような値だったとする (Windows 上の動作を仮定する)。
C:\lua\?.lua;C:\lua\?\init.lua
この場合、package.loaders[2]("foo") は以下のファイルを探索する。 (最初に見つかったものを用いる。)
複合名である場合、例えば package.loaders[2]("foo.bar") は以下のファイルを探索する。 ドット(.)がパス区切り(Windows なら \) に置き換えられることに注意。
このようにして取得した Lua モジュールファイルの中身は、 「モジュールローダーの関数の本体」である。 package.loaders[2] の戻り値はこの関数をコンパイルした結果の関数オブジェクトである。
先述の require() の解説で解るように、 モジュールローダーにはモジュール名「"foo"」 が引数に渡されているが、これは可変引数「...」 を利用して読み取ることができる。
基本的に以下の手順を踏む。
(i) を採用する場合:
-- モジュールとなるテーブルを構成する local m = {} function m.some_func() ...... end -- それを戻り値とする return m
(ii) を採用する場合:
-- モジュールとなるテーブルを構成する local m = {} function m.some_func() ...... end -- package.loaded に代入 package.loaded["foo"] = m
グローバル変数 foo にモジュールを格納するという慣習に従う場合は、 foo = m を実行することになる (あるいは最初から foo = {} として m の代わりに foo を用いて後の記述を行ってもよい。)
モジュールのファイル全体がローダー関数の本体で、 それにモジュール名が渡されているという性質を利用すると、 ファイルにモジュール名を記述する必要がなくなる。
local name = ... -- 第 1 引数がモジュール名である local m = {} package.loaded[name] = m -- (ii) の場合 _G[name] = m -- グローバルに格納する場合 m.greet_word = "Hello" function m.greeting(n) return m.greet_word .. ", " .. n .. "!" end function m.greet(n) print(m.greeting(n)) end
上の方法だと、モジュール内の公開の変数(関数も含む)に (自身の内部で)アクセスする時に、いつでもテーブルを示す変数名を付す (m.greet_word のように)必要が生じる。
この問題については、Lua の「関数の環境の切替」 (setfenv())を用いた解決法がある。 ローダー関数の環境をモジュール自身に切り替えると、 それ以降の「グローバルな定義」をモジュールに対する定義とすることができる。
local name = ... -- 第 1 引数がモジュール名である local m = {} package.loaded[name] = m -- (ii) の場合 _G[name] = m -- グローバルに格納する場合 setfenv(1, m) -- 「1」は現在実行中の関数を示す -- これ以降は「グローバル」がモジュール自身となる greet_word = "Hello" function greeting(n) return greet_word .. ", " .. n .. "!" end function greet(n) print(greeting(n)) -- ただしこれはダメ! end
この構成はある程度「標準的」と考えられるので、 Lua では 2~5 行目の操作を標準ライブラリ関数 module(name) として提供している。 従って、最初の 5 行の代わりに次の 1 行で済ませられる。
module(...)
(もちろん、module("foo") のように名前を指定してもよい。)
しかし、実は上のままだと、 greet() (グローバルには foo.greet()) は動作しない。 なぜなら、setfenv() した後では、 標準のグローバル環境が見えなくなっているので、 標準ライブラリの print() にアクセスできないからである。
標準ライブラリへのアクセスを可能にする簡単な方法は、 モジュールのテーブルを標準グローバル環境(_G) の継承オブジェクトとすることである。 つまり、setfenv(1, m) の前に次を実行する。
setmetatable(m, { __index = _G }) -- m を G の継承とする
これで、print() は標準グローバル環境のものにアクセスされ、 なおかつ、 新たに定義されるものは標準グローバル環境ではなくモジュールに格納されるようになる。
そして、module() 関数を利用する場合でも、 この設定を行う手段が用意されている。 module() の第 2 引数に package.seeall を渡せばよい。
module(..., package.seeall) -- この 1 行だけで全て上手くいく! greet_word = "Hello" function greeting(n) return greet_word .. ", " .. n .. "!" end function greet(n) print(greeting(n)) end
package.loaders[2]("foo") は以下の Kpathsea 検索で見つかるファイルを用いる。
kpsewhich --progname=luatex --format=lua foo.lua
複合名の場合はよく解らないが、 動作としては次のようになっているように見える。 例えば package.loaders[2]("foo.bar") は以下の Kpathsea 検索を順に行い最初に見つかるファイルを用いる。
kpsewhich --progname=luatex --format=lua foo/bar.lua kpsewhich --progname=luatex --format=lua foo.bar.lua
format=lua の場合に用いられる検索パスの Web2C 変数は LUAINPUTS である。
以上で述べたことは C モジュールローダー (package.loaders の 3~4 番目)でも同様だと思われる (確かめていないが)。 C モジュールについて、 kpsewhich の format の値は clua、 検索パスの変数は CLUAINPUTS である。
ここでは、luatexbase-modutils パッケージを使う場合の Lua モジュールの記述法について検討する。 なお、「資料-luatexbaseについて」での解説を前提とする。
luatexbase.require_module("foo") (または \RequireLuaModule{foo} ) は中で、require("foo") を呼ぶので、 luatexbase.require_module() によって呼ばれるものは通常の意味での Lua のモジュールでなければならないが、 その他にもいくつかの制約がある。
まず、luatexbase.require_module() は require() と異なり戻り値をもたない (TeX の \RequireLuaModule の方に「戻り値」 を持たせる術がないので当然) ので、グローバル変数への代入が必須となる。 従って、呼び出し側は以下のようになる。
(TeX 中で) \RequireLuaModule{foo.bar} (Lua 中で) luatexbase.require_module("foo.bar") (以降、グローバル変数 foo.bar がモジュールを指す)
先述の LuaTeX のローダー検索関数の動作より、 foo.bar パッケージは $LUAINPUTS 以下の foo/bar.lua または foo.bar.lua に記述することになる。
グローバル代入が必須ということを考えると、 結局 Lua の module() 関数を使うのが最も簡潔でまた妥当なようだ。 あと、luatexbase.require_module() での検査に備えて、luatexbase.provides_module() を実行する必要がある。 この引数での name 指定は、そもそも 「呼び出された名前と一致するかを検査する」 ためのものなので、「...」 から読み出すのではなく直接記述する。 (provides_module() の引数はできるだけ直接的な形で (値を変数で表す等はせず)書くのが望ましいと思う。) 対して、module() の引数は「...」 でもよいのかも知れない。 なお、module() はモジュール名を 「グローバル変数(実際にはモジュール内)」の _NAME に格納するので、 それ以降でモジュール名が必要になる場合は、 「...」ではなく _NAME を使うのがいいかも知れない。
luatexbase.provides_module({ name = 'foo.bar', date = '2011/01/01', version = '0.1a', description = 'Some foo and bar', }) module(..., package.seeall) local err, warn, info, log = luatexbase.errwarinf(_NAME)
ところで、luatexbase パッケージの Lua モジュールは少々奇妙なことをしている。 例えば、luatexbase-modutils をみる。
luatexbase-modutils.sty の中では、
require('luatexbase.modutils')
で Lua モジュールを読み込んでいる (当然ここで require_module() は使えない)。 しかし、Lua モジュールの中では、
module("luatexbase", package.seeall) -- "luatexbase.modutils" ではない!
となっている。 つまり、標準環境に luatexbase というモジュールが作られ、 後のグローバル定義はそこに格納される (だから luatexbase.require_module() なのであって、 luatexbase.modutils.require_module() にはなっていない)。 同時に、package.loaded["luatexbase"] にも登録される。 しかし、そのコードの中で、「luatexbase.modutils」 というモジュール(テーブル) はいかなる方法であっても作られず、 当然、グローバルの luatexbase.modutils や package.loaded["luatexbase.modutils"] にも何も行っていない。
つまり、この「モジュール」は、(i) にも (ii) にも従っていない (そもそも指定されたモジュールを作ってさえいない)。 かなり奇妙な事態であるが、しかし不正ではない。 この場合、package.loaded["luatexbase.modutils"] は true が入ることになる。
(ついでに言っておくと、luatexbase パッケージ全体を読み込むと、 module("luatexbase", package.seeall) が何度も実行されることになるが、 これも別に問題にはならない。 2 回目以降の module() では新たにテーブルは作られず、 1 回目に作られたテーブルが再利用され、 従ってそこに新たな定義が加わることになる。