メタテーブルは、テーブルを以前よりもパワフルにすることを可能にします。これらはデータに付加され、メタメソッドと呼ばれる値を含みます。メタメソッドは、それに付加されたデータに対して特定のアクションが使用されるときに発火します。
メタテーブルを操作する
テーブルのメタテーブルを追加および取得するための主な二つの関数は setmetatable() と getmetatable() です。
local x = {}local metaTable = {} -- メタテーブルもテーブルです!setmetatable(x, metaTable) -- xにmetaTableというメタテーブルを与えます!print(getmetatable(x)) --> table: [16進数メモリアドレス]
setmetatable() 関数は、メタテーブルを設定しているテーブルを返すため、これらの二つのスクリプトは同じことを行います:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
メタメソッド
メタメソッドは、メタテーブルの中に格納されている関数です。テーブルを呼び出すことから、テーブルを加算すること、さらにはテーブルを割り算することさえも可能にします。以下は利用可能なメタメソッドのリストです:
| メソッド | 説明 |
|---|---|
| __index(table, index) | table[index] がインデックスされると発火し、table[index] が nil の場合に発火します。テーブルに設定することも可能で、その場合はそのテーブルがインデックスされます。 |
| __newindex(table, index, value) | table[index] を設定しようとしたとき (table[index] = value) に、table[index] が nil の場合に発火します。テーブルに設定することも可能で、その場合はそのテーブルがインデックスされます。 |
| __call(table, ...) | テーブルが関数のように呼び出されたときに発火し、... は渡された引数です。 |
| __concat(table, value) | テーブル上で .. 連結演算子が使用されたときに発火します。 |
| __unm(table) | テーブル上で単項 – 演算子が使用されたときに発火します。 |
| __add(table, value) | + 加算演算子。 |
| __sub(table, value) | – 減算演算子。 |
| __mul(table, value) | * 乗算演算子。 |
| __div(table, value) | / 除算演算子。 |
| __idiv(table, value) | // フロア除算演算子。 |
| __mod(table, value) | % 剰余演算子。 |
| __pow(table, value) | ^ 累乗演算子。 |
| __tostring(table) | テーブルに対して tostring が呼ばれたときに発火します。 |
| __metatable | 存在する場合、メタテーブルをロックして getmetatable() がこれを返す代わりにメタテーブルを返すようにし、setmetatable() がエラーを出します。非関数値。 |
| __eq(table, value) | == 等しい演算子¹ |
| __lt(table, value) | < より小さい演算子¹ |
| __le(table, value) | <= 演算子¹ |
| __mode | 弱いテーブルで使用され、テーブルのキーと/または値が弱いかどうかを宣言します。Robloxのインスタンスへの参照は決して弱くないことに注意してください。このような参照を保持するテーブルは、決してガベージコレクションされません。 |
| __len(table) | オブジェクトに対して # 長さ演算子が使用されたときに発火します。 |
| __iter(table) | 一般化されたイテレーションを使用する際にカスタムイテレータを示すために使用されます。 |
算術または関係メタメソッドのために関数を書くときの二つの関数パラメータは、メタメソッドを発火させたテーブルと他の値の間で入れ替え可能であることに注意すべきです。例えば、ベクトル操作をスカラーと実行する場合、除算は可換ではありません。したがって、自分の vector2 クラス用のメタメソッドを書く場合は、どちらのシナリオも考慮するように注意が必要です。
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- aはスカラー、bはベクトル
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- aはベクトル、bはスカラー
local vector, scalar = a, b
return vector2.new(vector.x / scalar, vector.y / scalar)
elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
-- aもbもベクトル
return vector2.new(a.x / b.x, a.y / b.y)
end
end
function mt.__tostring(t)
return t.x .. ", " .. t.y
end
function vector2.new(x, y)
local self = setmetatable({}, mt)
self.x = x or 0
self.y = y or 0
return self
end
local a = vector2.new(10, 5)
local b = vector2.new(-3, 4)
print(a / b) -- -3.3333333333333, 1.25
print(b / a) -- -0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5
使用法
メタテーブルを使用する方法は多く、例えばテーブルを負の数にするための __unm メタメソッドなどがあります:
local metatable = {
__unm = function(t) -- __unm は単項 - 演算子のためのもの
local negated = {}
for key, value in t do
negated[key] = -value -- このテーブルのすべての値を否定します
end
return negated -- テーブルを返します
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
以下は __index を使用して物事を宣言する興味深い方法です:
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
x がテーブル内でインデックスされなかったとき、__index が発火しました。Luau はその後 __index テーブルを検索し、インデックス x を見つけて返しました。
これを単純な関数で簡単に行うことができますが、さらに多くの可能性があります。例えば、次のようにします:
local t = {10, 20, 30}print(t(5))
一般にはテーブルを呼び出すことはできませんが、メタテーブルを使うとやることができます:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- 引数 (5) を値に加え、その後新しいテーブル (t) に置きます。
end
return unpack(sum) -- 個々のテーブルの値を返します
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
テーブルを加算するなど、さまざまなことが可能です:
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
これを実行すると、テーブル上で算術を行おうとしているというエラーが発生しますが、メタテーブルを使うと動作します:
local metatable = {
__add = function(t1, t2)
local sum = {}
for key, value in t1 do
sum[key] = value
end
for key, value in t2 do
if sum[key] then
sum[key] += value
else
sum[key] = value
end
end
return sum
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
local table2 = setmetatable({13, 14, 15}, metatable)
for k, v in table1 + table2 do
print(k, v)
end
メタテーブルを扱っていると、いくつかの問題に直面することがあります。もし __index メタメソッドを使用してテーブルに新しい値を作成する必要があるが、そのテーブルのメタテーブルにも __newindex メタメソッドがある場合、値を設定する際にメタメソッドを呼び出すことなく、rawset() の Luau ビルトイン関数を使用する必要があります。以下のコードを見てください。
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- 標準の方法でテーブルに値を設定しないでください
end
})
print(t[1]) -- スタックオーバーフローを引き起こします
スタックオーバーフローは、関数を自身から呼び出しすぎると発生します。上記の __index 関数では、self[i] が値に設定されると、次の行で self[i] が存在するはずで、通常は __index メタメソッドが呼ばれないはずです。問題は、__newindex が値を設定させないことです。この存在は、標準の t[i] = v メソッドでテーブルに値を追加するのを阻止します。この問題を回避するために rawset() 関数を使用します:
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- 標準の方法でテーブルに値を設定しないでください
end
})
print(t[1]) --> 10
セットデータ型を使用する
セット は、順序のない項目の集まりであり、重複する要素はありません。項目は、セットに 含まれる または 含まれない ことができます。メタテーブルを使用することで、Luau スクリプト内でセットを構築し操作することができます。
基本メソッド
以下のコードは、基本的なセットの機能を含み、新しいセットの構築、項目の追加および削除、セットが項目を含むかどうかの確認、およびセットの内容の出力ができます。
local Set = {}
Set.__index = Set
-- 項目のオプションリストからセットを構築する関数
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- セットに項目を追加する関数
function Set:add(item)
self[item] = true
end
-- セットから項目を削除する関数
function Set:remove(item)
self[item] = nil
end
-- セットが項目を含むかどうかを確認する関数
function Set:contains(item)
return self[item] == true
end
-- デバッグのためにセットをカンマ区切りリストとして出力する関数
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
セットを作成する
新しいセットは、オプションの項目配列を追加して Set.new() を呼び出すことで構築できます。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
定義上、セットには順序の概念はありません。
項目を追加する
既存のセットに項目を追加するには、Set:add() メソッドを使用できます。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:add("Mango")
項目を削除する
セットから項目を削除するには、項目名を指定して Set:remove() を呼び出します。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:remove("Orange")
項目を確認する
セットが特定の項目を含むかどうかを確認するには、Set:contains() を使用します。
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})local result1 = fruits:contains("Cherry")print(result1) -- truelocal result2 = fruits:contains("Watermelon")print(result2) -- false
追加メソッド
セット間で項目を比較したり、セットを結合したり、別のセットから項目を引き算したりするために、他の便利な操作を実装できます。
積集合
セットをベン図として考えると、二つのセットの 積集合 は次のように取得できます。これは、両方 のセットに出現する項目を意味します。
local function getIntersection(set1, set2)
local result = Set.new()
for key, value in set1 do
if set2:contains(key) then
result:add(key)
end
end
return result
end
local freshFruits = Set.new({"Mango", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local frozenFruits = Set.new({"Mango", "Peach", "Pineapple"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mango, Peach
和集合
次の関数を使用して二つのセットの 和集合 を取得できます。これは、両方のセットに含まれる項目の重複のない集合を意味します。この関数は、__add メタメソッドを使用して set1 + set2 の加算ショートカットを提供します。
function Set:__add(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = true
end
return result
end
local sweetFruits = Set.new({"Apple", "Mango", "Cherry", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Peach, Lime, Apple, Cherry, Lemon, Mango
差集合
一つのセットから別のセットのすべての項目を取り除くことができます。この関数は上記の関数に似ており、__sub メタメソッドを使用して set1 - set2 の減算ショートカットを提供します。
function Set:__sub(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = nil
end
return result
end
local allFruits = Set.new({"Apple", "Lemon", "Mango", "Cherry", "Lime", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apple, Cherry, Peach