メタテーブルは、テーブルを以前よりも強力にすることができます。データに付加され、メタメソッドと呼ばれる値を含んでいます。メタメソッドは、特定のアクションがそのデータに対して使用されると発火します。
メタテーブルを操作する
テーブルのメタテーブルを追加および取得するための主な関数は setmetatable() と getmetatable() です。
local x = {}
local metaTable = {} -- メタテーブルもテーブルです!
setmetatable(x, metaTable) -- xにmetaTableというメタテーブルを与えます!
print(getmetatable(x)) --> table: [16進数のメモリアドレス]setmetatable() 関数は、メタテーブルを設定しているテーブルを返すため、これらの2つのスクリプトは同じことを行います:
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 | 弱いテーブルで使用され、テーブルのキーおよび/または値が弱いかどうかを宣言します。 弱いテーブルの使用を参照してください。 |
| __len(table) | #長さ演算子がオブジェクトに対して使用されたときに発火します。 |
| __iter(table) | 一般化された反復を使用する際にカスタムイテレータを示すために使用されます。 |
算術または関係メタメソッドのために関数を書くとき、2つの関数パラメータは、メタメソッドを発火させたテーブルと他の値の間で入れ替え可能であることに注意する必要があります。たとえば、スカラーとのベクトル演算を行う場合、除算は可換ではありません。したがって、独自の 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) --> 1xがテーブル内でインデックス付けされて見つからなかったときに__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 do
print(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弱いテーブルを使用する
テーブル、関数、スレッド、および ユーザーデータ を保持する変数は、それらのオブジェクトのコピーではなく、参照を保存します。オブジェクトを別の変数に割り当てると、同じオブジェクトへの別の参照が作成されます:
local map = {key = 1}
local mapReference = map
print(map == mapReference) --> true
print(map == {key = 1}) --> false
local function func() end
local funcReference = func
print(func == funcReference) --> true
local part = Instance.new("Part")
local partReference = part
print(part == partReference) --> true強い参照を介して到達できなくなったオブジェクトは、ガーベジコレクションの対象になります。1つの変数をnilに設定しても、別の強い参照がそのオブジェクトを指している限り、そのオブジェクトはガーベジコレクションの対象にはなりません:
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [16進数のメモリアドレス]childが子テーブルを指していなくなったとしても、parent.Childはまだそれを指しています。親テーブルが到達可能である限り、その参照は子テーブルを到達可能に保ちます。
弱いテーブルは、ガーベジコレクタがそれ以外の到達不可能なオブジェクトを回収するのを妨げずに、キー、値、またはその両方を保持します。テーブルのメタテーブルの__modeフィールドを設定して、どの参照が弱いかを制御します:
- "k"はキーを弱くします。
- "v"は値を弱くします。
- "kv"または"vk"は、キーと値の両方を弱くします。
以下のコードサンプルは、弱い値を持つテーブルを作成します:
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
enddoブロックが終了すると、children.exampleに格納されている値は強い参照を持たず、ガーベジコレクションの対象になります。ガーベジコレクタが子テーブルを回収すると、Luauはchildrenからそのエントリを削除します。ガーベジコレクションは非決定的であるため、エントリが消えるタイミングに依存しないでください。
省略したモードは強いままです。__mode = "v"テーブルのキーは強い参照のままであるため、キーとして格納されたオブジェクトは到達可能であり、そのエントリはテーブルに残ります。
Instancesの場合、ガーベジコレクションはLuauの参照に適用され、オブジェクト自体には適用されません。エンジンはインスタンスデータをLuau VMの外部に保存するため、Luauの参照を収集してもインスタンスは削除されず、データモデルに残るインスタンスはそのLuauの参照を生かし続けません。インスタンスを解放するには、Instance:Destroy()を呼び出し、それに対するすべての残りの参照をクリアします。
セットデータ型を使用する
セットは、順序がなく重複要素がないアイテムのコレクションです。アイテムは、セットに含まれているか含まれていないかのいずれかです。メタテーブルを使用して、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) -- true
local result2 = fruits:contains("Watermelon")
print(result2) -- false追加メソッド
セット間でアイテムを比較したり、セットを結合したり、1つのセットから別のセットを引いたりするための他の便利な操作を実装できます。
積集合
セットをベン図として考えると、2つのセットの積集合を次のように取得できます。これは、両方のセットに出現するアイテムを意味します。
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和集合
次の関数を使用して、2つのセットの和集合を取得できます。これは、重複のない両方のセットのアイテムのコレクションを意味します。この関数は、メタテーブルの__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差集合
次の関数を使用して、1つのセットのすべてのアイテムを別のセットのアイテムから削除できます。上記の関数と同様に、これはメタテーブルの__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