この第 1 版は Lua 5.0 向けに執筆されました。後続のバージョンでもまだ大部分が関連していますが、いくつかの相違点があります。
第 4 版は Lua 5.3 を対象としており、Amazon やその他の書店で購入できます。
本を購入することで、Lua プロジェクトのサポートにも貢献できます。


15.4 – グローバルテーブルの利用

パッケージを作成するためのこれらすべてのメソッドの欠点は、プログラマ側の特別な注意を要することです。たとえば、宣言時に local を忘れるのはよくあることです。グローバル変数のテーブル内のメタメソッドは、パッケージを作成するための非常に興味深い代替手法を提供します。これらすべての技術の共通点は、パッケージ用の排他的な環境を使用することです。これは簡単に実行できます。パッケージのメインチャンクの環境を変更すると、そのチャンクが作成するすべての関数は、この新しい環境を共有します。

最も単純な手法はそれ以上のことではありません。パッケージに排他的な環境が用意されたら、すべての関数はこのテーブルを共有するだけでなく、そのグローバル変数もすべてこのテーブルに移動されます。したがって、すべての公開関数をグローバル変数として宣言できます。すると、自動的に別のテーブルに移動されます。パッケージが行う必要のあるのは、このテーブルをパッケージ名として登録することだけです。次のコードフラグメントは、complex ライブラリに関するこの技術を示しています。

    local P = {}
    complex = P
    setfenv(1, P)
これで、関数 add を宣言すると、complex.add に移動します
    function add (c1, c2)
      return new(c1.r + c2.r, c1.i + c2.i)
    end
さらに、接頭辞なしでこのパッケージから他の関数も呼び出すことができます。たとえば、addnew をその環境から取得します。つまり、complex.new を取得します。

この方法はパッケージに優れたサポートを提供し、プログラマーの余分な作業はほとんど必要ありません。接頭辞はまったく必要ありません。エクスポートされた関数とプライベート関数の呼び出しには違いがありません。プログラマーが local を忘れた場合、グローバル名前空間が汚染されることはありません。代わりに、プライベート関数のみが公開されます。さらに、前のセクションの手法と組み合わせてパッケージ名に使用できます

    local P = {}   -- package
    if _REQUIREDNAME == nil then
      complex = P
    else
      _G[_REQUIREDNAME] = P
    end
    setfenv(1, P)

もちろん、欠けているのは他のパッケージへのアクセスです。空のテーブル P を環境にすると、それ以前のグローバル変数にはアクセスできなくなります。これにはいくつかの解決策があり、それぞれに長所と短所があります。

最も単純な解決策は、前に見たように継承することです

    local P = {}   -- package
    setmetatable(P, {__index = _G})
    setfenv(1, P)
(setfenv を呼び出す前に setmetatable を呼び出す必要があります。その理由はわかりますか?) この構造を使用すると、パッケージは任意のグローバル識別子に直接アクセスできますが、アクセスごとにわずかなオーバーヘッドが支払われます。この解決策の興味深い結果として、概念的には、パッケージにはすべてのグローバル変数が含まれるようになりました。たとえば、パッケージを使用する人が標準のサイン関数を呼び出す場合、complex.math.sin(x) と記述する可能性があります。(Perl のパッケージシステムにもこの癖があります。)

他のパッケージにアクセスするもう 1 つの簡単な方法は、古い環境を保持する local を宣言することです

    local P = {}
    pack = P
    local _G = _G
    setfenv(1, P)
外部の名前へのアクセスに _G. をプレフィックスとして指定する必要がありますが、メタメソッドに関与しないため、より高速にアクセスできます。継承とは異なり、この方法では以前の環境への書き込みアクセスを行うことができます。これが良いことか悪いことかは議論の余地がありますが、柔軟性が必要になる場合があります。

より厳密なアプローチは、ローカルとして必要な関数のみ、または必要に応じてパッケージのみを宣言することです。

    local P = {}
    pack = P
    
    -- Import Section:
    -- declare everything this package needs from outside
    local sqrt = math.sqrt
    local io = io
    
    -- no more external access after this point
    setfenv(1, P)
この手法ではより多くの作業が必要になりますが、パッケージの依存関係がさらに詳しくドキュメント化されます。また、以前のスキーマよりも高速なコードになります。