Luaはパッケージのための明示的なメカニズムを提供していません。しかし、言語が提供する基本的なメカニズムを使用して、それらを簡単に実装できます。実際、それを行う方法はいくつかあり、それが問題を生み出します。Luaでパッケージを作成するための標準的な方法がないのです。さらに、ルールに従うかどうかはあなた次第であり、パッケージを実装するための固定された方法も、それらを操作するための固定された操作もありません。
Luaでは、テーブルを使用してパッケージを実装する方が良い解決策です。グローバル変数としてではなく、識別子をテーブルのキーとして配置するだけです。ここで重要なのは、他の値と同様に、関数をテーブルの中に格納できることです。たとえば、複素数を操作するライブラリを作成するとします。各数を`r`(実数部)と`i`(虚数部)のフィールドを持つテーブルで表します。グローバル名前空間を汚染しないために、新しい操作をすべて新しいパッケージとして機能するテーブルに宣言します。
Complex = {} Complex.i = {r=0, i=1} function Complex.new (r, i) return {r=r, i=i} end function Complex.add (c1, c2) return {r=c1.r+c2.r, i=c1.i+c2.i} end function Complex.sub (c1, c2) return {r=c1.r-c2.r, i=c1.i-c2.i} end function Complex.mul (c1, c2) return {r = c1.r*c2.r - c1.i*c2.i, i = c1.r*c2.i + c1.i*c2.r} end function Complex.inv (c) local n = c.r^2 + c.i^2 return {r=c.r/n, i=c.i/n} end
この定義により、次のように操作名を修飾して複素数演算を使用できます。
c = Complex.add(Complex.i, Complex.new(10, 20))
テーブルによるパッケージの使用は、実際のパッケージによって提供される機能と完全に同じではありません。Luaでは、すべての関数定義にパッケージ名を明示的に付ける必要があります。さらに、同じパッケージ内の別の関数を呼び出す関数は、呼び出される関数の名前を修飾する必要があります。パッケージの固定されたローカル名(たとえば`Public`)を使用し、このローカル名をパッケージの最終名に割り当てることで、これらの問題を改善できます。このガイドラインに従って、前の定義は次のように記述します。
local Public = {} Complex = Public -- package name Public.i = {r=0, i=1} function Public.new (r, i) return {r=r, i=i} end ...関数が同じパッケージ内の別の関数を呼び出す場合(または再帰的に自身を呼び出す場合)、呼び出される関数にはパッケージのローカル名のアップ値を通してアクセスする必要があります。たとえば、
function Public.div (c1, c2) return %Public.mul(c1, %Public.inv(c2)) endこれらのガイドラインに従うと、2つの関数間の接続はパッケージ名に依存しなくなります。さらに、パッケージ名を書いているのはパッケージ全体で1箇所だけです。
通常、パッケージ内のすべての名前はエクスポートされます。つまり、パッケージのクライアントが使用できます。しかし、場合によっては、パッケージ自体だけが使用できるプライベート名、つまりパッケージ内部でのみ使用できる名前を持つことが役立ちます。これを行う便利な方法は、パッケージ内のプライベート名用に別のローカルテーブルを定義することです。このようにして、パッケージをパブリック名用のテーブルとプライベート名用のテーブルの2つのテーブルに分割します。パブリックテーブルをグローバル変数(パッケージ名)に割り当てているため、そのすべてのコンポーネントは外部からアクセスできます。しかし、プライベートテーブルをグローバル変数に割り当てていないため、パッケージ内部にロックされたままになります。この手法を説明するために、値が有効な複素数であるかどうかをチェックするプライベート関数を例に追加してみましょう。私たちの例は次のようになります。
local Public, Private = {}, {} Complex = Public function Private.checkComplex (c) assert((type(c) == "table") and tonumber(c.r) and tonumber(c.i), "bad complex number") end function Public.add (c1, c2) %Private.checkComplex(c1); %Private.checkComplex(c2); return {r=c1.r+c2.r, i=c1.i+c2.i} end ...
では、このアプローチの長所と短所は何でしょうか?パッケージ内のすべての名前は、個別の名前空間に存在します。パッケージ内の各エンティティは、パブリックまたはプライベートとして明確にマークされています。さらに、真のプライバシーがあります。プライベートエンティティは、パッケージの外部からアクセスできません。このアプローチの主な欠点は、同じパッケージ内の他のエンティティにアクセスする場合の長文です。すべてのアクセスには接頭辞(`%Public.`または`%Private.`)が必要です。長文であるにもかかわらず、これらのアクセスは非常に効率的です。また、これらの2つの変数に対してより短いエイリアス(`local E, I = Public, Private`など)を提供することで、この長文を軽減できます。関数の状態をパブリックとプライベートの間で変更するたびに接頭辞を変更しなければならないという問題もあります。それにもかかわらず、私は全体としてこのアプローチが好きです。私にとって、負の側面(冗長さ)は、言語のシンプルさによって十分に補われています。結局のところ、言語から追加の機能を必要とすることなく、非常に満足のいくパッケージシステムを実装できます。
パッケージの実装にテーブルを使用することの明白な利点は、他のテーブルと同様にパッケージを操作し、Luaの全機能を使用して追加の機能を作成できることです。可能性は無限にあります。ここでは、いくつかの提案を示すにとどめます。
パッケージのパブリックアイテムをすべてまとめて定義する必要はありません。たとえば、別のチャンクで`Complex`パッケージに新しいアイテムを追加できます。
function Complex.div (c1, c2) return %Complex.mul(c1, %Complex.inv(c2)) end(ただし、プライベート部分は1つのファイルに制限されていることに注意してください。これは良いことだと思います。)逆に、同じファイルに複数のパッケージを定義できます。行う必要があるのは、各パッケージを`do ... end`ブロックで囲み、その`Public`変数と`Private`変数がそのブロックに制限されるようにすることだけです。
いくつかの操作を頻繁に使用する場合、グローバル(またはローカル)名を与えることができます。
add = Complex.add local i = Complex.i c1 = add(Complex.new(10, 20), i)または、パッケージ全体の名前を何度も書く必要がない場合は、パッケージ全体に短いローカル名を一度に付けることができます。
local C = Complex c1 = C.add(C.new(10, 20), C.i)
パッケージ全体を開き、そのすべての名前をグローバル名前空間に配置する関数を簡単に記述できます。
function openpackage (ns) for n,v in ns do setglobal(n,v) end end
openpackage(Complex) c1 = mul(new(10, 20), i)パッケージを開く際の名称衝突が心配な場合は、割り当ての前に名前を確認できます。
function openpackage (ns) for n,v in ns do if getglobal(n) ~= nil then error(format("name clash: `%s' is already defined", n)) end setglobal(n,v) end end
パッケージ自体はテーブルであるため、パッケージをネストすることもできます。つまり、別のパッケージの中にパッケージ全体を作成できます。ただし、そのような機能はめったに必要ありません。
通常、パッケージを作成する場合、そのコード全体を単一ファイルに配置します。次に、パッケージを開くまたはインポートする(つまり、使用可能にする)には、そのファイルを実行するだけです。たとえば、`complex.lua`というファイルに複素数パッケージの定義がある場合、`dofile("complex.lua")`というコマンドを実行すると、パッケージが開きます。パッケージが複数回ロードされた場合の無駄を避けるために、パッケージが既にロードされているかどうかをチェックしてパッケージを開始できます。
if Complex then return end local Public, Private = {}, {} Complex = Public ...これで、`Complex`が既に定義されているときに`dofile("complex.lua")`を実行すると、ファイル全体がスキップされます。(注:Lua 4.1で使用可能になる新しい関数`require`により、このチェックは不要になります。)