2.4 メタテーブルとメタメソッド
Lua の全ての値はメタテーブル (metatable) を持つことができます。このメタテーブルは通常の Lua テーブルであり、関連付いた値の特定のイベントに対する振る舞いを定義します。値が持つメタテーブルの特定のフィールドを設定することで、様々な場面における値の振る舞いを変更できます。例えば数値でない値が加算演算のオペランドになった場合には、Lua はその値のメタテーブルの __add フィールドに関数があるかを調べ、もし見つかればその関数を呼んで「加算」を実行します。
あるイベントに対応するメタテーブルのフィールドのキーは、イベントの名前の先頭にアンダースコアを二つ付けた文字列です。対応するバリューはメタバリュー (metavalue) と呼ばれます。たいていのイベントではメタバリューが関数であり、この場合はその関数をメタメソッド (metamethod) と呼びます。ただし特別な設定をしなければ任意の呼び出せる値をメタメソッドに設定でき、関数だけではなく __call メタメソッドを持つ値もメタメソッドにできます。
任意の値のメタテーブルは getmetatable 関数を使って確認できます。Lua がメタテーブル内のメタメソッドを問い合わせるときは直接アクセスが使われます (参照: rawget)。
テーブル型の値のメタテーブルは setmetatable 関数で設定できます。テーブル以外の型の値については、メタテーブルを Lua コードから変更することはできません。ただし debug ライブラリを使えば行えます (参照: § 6.10)。
テーブルとフルユーザーデータはそれぞれの値が個別のメタテーブルを持ちますが、複数のテーブルやフルユーザーデータが同じメタテーブルを共有することは可能です。他の全ての型では型ごとに一つのメタテーブルがあり、それが全ての値で共有されます。例えば全ての数値で使われるメタテーブルが一つあり、全ての文字列で使われるメタテーブルが一つあるといった形です。デフォルトではどんな値もメタテーブルを持ちませんが、文字列ライブラリは文字列型の値に対するメタテーブルを設定します (参照: § 6.4)。
メタテーブルが管理する処理の詳しいリストを次に示します。各イベントは対応するキーで識別されます。慣習により、Lua が使うメタテーブルのキーは二つのアンダースコアと小文字アルファベットからなります。
__add: 加算演算 (+)。二つのオペランドのいずれかが数値でない場合、Lua はメタメソッドの呼び出しを試みます。一つ目のオペランドを最初に確認し、__addに対するメタメソッドが定義されていなければ二つ目のオペランドを確認します (確認は数値に対しても行われます)。メタメソッドを見つけると、Lua は二つのオペランドを引数としてそれを呼び出し、呼び出しの結果 (を一つに調整したもの) を演算の結果とします。メタメソッドがどちらからも見つからなければ、Lua はエラーを送出します。__sub: 減算演算 (-)。処理は加算演算と同様です。__mul: 乗算演算 (*)。処理は加算演算と同様です。__div: 除算演算 (/)。処理は加算演算と同様です。__mod: モジュロ演算 (%)。処理は加算演算と同様です。__pow: 指数演算 (^)。処理は加算演算と同様です。__unm: 否定演算 (単項-)。処理は加算演算と同様です。__idiv: 切り捨て除算演算 (//)。処理は加算演算と同様です。__band: ビットごとの AND 演算 (&)。処理は加算と同様ですが、メタメソッドが確認されるのはいずれかのオペランドが整数でも整数へ強制変換可能な浮動小数点数でもないときだけです。__bor: ビットごとの OR 演算 (|)。処理はビットごとの AND 演算と同様です。__bxor: ビットごとの XOR 演算 (二項~)。処理はビットごとの AND 演算と同様です。__bnot: ビットごとの NOT 演算 (単項~)。処理はビットごとの AND 演算と同様です。__shl: ビットごとの左シフト演算 (<<)。処理はビットごとの AND 演算と同様です。__shr: ビットごとの右シフト演算 (>>)。処理はビットごとの AND 演算と同様です。__concat: 連結演算 (..)。処理は加算演算と同様ですが、メタメソッドが確認されるのはオペランドが文字列でも数値でもないときだけです (数値は必ず文字列に変換できます)。-
__len: 長さの取得演算 (#)。Lua はオブジェクトが文字列でないときにメタメソッドを確認します。メタメソッドが存在するなら演算のオペランドを引数としてそれを呼び、その結果 (を一つの値に調整したもの) を演算の結果とします。メタメソッドが存在しない場合でもオペランドがテーブルであれば、Lua はテーブルの長さ取得演算を行います (参照: § 3.4.7)。それ以外の場合はエラーを送出します。 __eq: 等号演算 (==)。処理は加算演算と同様ですが、Lua がメタメソッドを確認するのは比較されている二つのオペランドが両方ともテーブルあるいは両方ともフルユーザーデータであって、それらが原始的に等しくないときだけです。メタメソッドの結果は真偽値に変換されます。__lt: 小なり演算 (<)。処理は加算演算と同様ですが、メタメソッドが確認されるのはオペランドが「両方とも数値または両方とも文字列」でないときだけです。__le: 小なり等号演算 (<=)。処理は小なり演算と同様です。-
__index: 添え字アクセス演算 (table[key])。このイベントはtableがテーブルでないとき、およびkeyがtableに存在しないときに起こります。tableのメタテーブルからメタバリューが検索されます。このイベントに対するメタバリューには関数・テーブル・
__indexメタバリューを持った任意の値のどれかを設定します。メタバリューが関数である場合には、その関数がtableとkeyを引数として呼ばれ、呼び出しの結果 (を一つの値に調整したもの) が演算の結果となります。それ以外の場合にはメタバリューに添え字keyでアクセスした結果が添え字演算の結果となります。後者の場合のアクセスは直接的でない通常のアクセスであり、これによってさらに__indexイベントが起こる可能性があります。 -
__newindex: 添え字代入 (table[key] = value)。添え字アクセスイベントと同様このイベントもtableがテーブルでないとき、およびkeyがtableに存在しないときに起こります。tableのメタテーブルからメタバリューが検索されます。添え字アクセスイベントと同様に、このイベントに対するメタバリューには関数・テーブル・
__newindexを持つ値のどれかを設定します。メタバリューが関数の場合には、その関数がtable,key,valueを引数として呼ばれます。そうでない場合には、同じキーとバリューを使った添え字代入イベントをメタバリューに対してもう一度行います。後者の場合の代入は直接的でない通常の代入であり、これによってさらに__newindexイベントが起こる可能性があります。__newindexイベントが起こるとき、Lua は原始的な代入を行いません。もし必要な場合ならメタメソッドでrawsetを呼べば原始的な代入を行えます。 __call: 呼び出し演算 (func(args))。このイベントは Lua が関数でない値funcを呼び出そうとしたときに発生します。funcの__callメタメソッドが検索され、もし存在すれば呼び出されます。funcが一つ目の引数となり、その後に元の引数argsが続きます。この呼び出しの全ての結果が演算の結果となります。これは複数の結果を返せる唯一のメタメソッドです。
ここに示したキーに加えて、Lua インタープリタは __gc (§ 2.5.3)・__close (§ 3.3.8)・__mode (§ 2.5.4)・__name にも対応します。__name エントリーに設定された文字列は tostring やエラーメッセージで利用されることがあります。
単項演算子 (否定・長さの取得・ビットごとの NOT) では、メタメソッドはダミーの第二引数 (第一引数と同じ値) と共に呼ばれます。この追加のオペランドは Lua の内部コードを単純にする (単項演算子を二項演算子と同じように扱う) ためだけにあるので、大部分のユーザーはこれを気にしなくて構いません。
メタテーブルは通常のテーブルと同じなので、上で定義したイベント名でないフィールドも保持できます。標準ライブラリに含まれる関数の一部 (例えば tostring) はメタテーブルの他のフィールドを利用します。
テーブルに必要なメタメソッドを全て追加してからオブジェクトのメタテーブルを設定するのが良い習慣です。特に __gc メタメソッドはこの順番で設定した場合にのみ意味を持ちます (参照: § 2.5.3)。メタテーブルをオブジェクトを作成した直後に設定するのも良い習慣です。