Les métatables permettent aux tables de devenir plus puissantes qu'auparavant. Elles sont attachées aux données et contiennent des valeurs appelées métaméthodes. Les métaméthodes sont déclenchées lorsqu'une certaine action est utilisée avec la donnée à laquelle elles sont attachées.
Manipuler les métatables
Les deux fonctions principales pour ajouter et trouver la métatable d'une table sont setmetatable() et getmetatable().
local x = {}local metaTable = {} -- Les métatables sont aussi des tables !setmetatable(x, metaTable) -- Donnez à x une métatable appelée metaTable !print(getmetatable(x)) --> table: [adresse mémoire hexadécimale]
La fonction setmetatable() renvoie également la table à laquelle vous définissez la métatable, donc ces deux scripts font la même chose :
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
Métaméthodes
Les métaméthodes sont les fonctions qui sont stockées à l'intérieur d'une métatable. Elles peuvent passer d'un appel à une table, à l'ajout d'une table, jusqu'à la division de tables également. Voici la liste des métaméthodes disponibles :
| Méthode | Description |
|---|---|
| __index(table, index) | Se déclenche lorsque table[index] est indexé, si table[index] est nil. Peut également être défini sur une table, auquel cas cette table sera indexée. |
| __newindex(table, index, value) | Se déclenche lorsque table[index] essaie d'être défini (table[index] = value), si table[index] est nil. Peut également être défini sur une table, auquel cas cette table sera indexée. |
| __call(table, ...) | Se déclenche lorsque la table est appelée comme une fonction, ... sont les arguments qui ont été passés. |
| __concat(table, value) | Se déclenche lorsque l'opérateur de concaténation .. est utilisé sur la table. |
| __unm(table) | Se déclenche lorsque l'opérateur unaire – est utilisé sur la table. |
| __add(table, value) | L'opérateur d'addition +. |
| __sub(table, value) | L'opérateur de soustraction –. |
| __mul(table, value) | L'opérateur de multiplication *. |
| __div(table, value) | L'opérateur de division /. |
| __idiv(table, value) | L'opérateur de division entière //. |
| __mod(table, value) | L'opérateur de modulo %. |
| __pow(table, value) | L'opérateur d'exponentiation ^. |
| __tostring(table) | Se déclenche lorsque tostring est appelé sur la table. |
| __metatable | Si présent, verrouille la métatable afin que getmetatable() renvoie ceci au lieu de la métatable et que setmetatable() génère une erreur. Valeur non fonctionnelle. |
| __eq(table, value) | L'opérateur == d'égalité¹ |
| __lt(table, value) | L'opérateur < de comparaison inférieure¹ |
| __le(table, value) | L'opérateur <=¹ |
| __mode | Utilisé dans les tables faibles, déclarant si les clés et/ou les valeurs d'une table sont faibles. Notez que les références aux instances Roblox ne sont jamais faibles. Les tables qui détiennent de telles références ne seront jamais collectées par le ramasse-miettes. |
| __len(table) | Se déclenche lorsque l'opérateur # de longueur est utilisé sur l'objet. |
| __iter(table) | Utilisé pour indiquer un itérateur personnalisé lors de l'utilisation de l'itération généralisée. |
Il convient de noter que lors de l'écriture de fonctions pour des métaméthodes arithmétiques ou relationnelles, les deux paramètres de fonction sont interchangeables entre la table qui a déclenché la métaméthode et l'autre valeur. Par exemple, lors de l'exécution d'opérations vectorielles avec des scalaires, la division n'est pas commutative. Par conséquent, si vous écriviez des métaméthodes pour votre propre classe vector2, vous voudriez être prudent afin de tenir compte de chaque scénario.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a est un scalaire, b est un vecteur
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a est un vecteur, b est un scalaire
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 et b sont tous deux des vecteurs
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
Utilisation
Il existe de nombreuses façons d'utiliser des métatables, par exemple la métaméthode __unm pour rendre une table négative :
local metatable = {
__unm = function(t) -- __unm est pour l'opérateur unaire -
local negated = {}
for key, value in t do
negated[key] = -value -- négate tous les valeurs dans cette table
end
return negated -- retourne la table
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
Voici une façon intéressante de déclarer des choses en utilisant __index :
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
__index a été déclenché lorsque x a été indexé dans la table et n'a pas été trouvé. Luau a ensuite cherché dans la table __index un index appelé x, et, en en trouvant un, le renvoie.
Vous pouvez maintenant facilement faire cela avec une simple fonction, mais il y a beaucoup plus à faire. Prenez ceci par exemple :
local t = {10, 20, 30}print(t(5))
Typiquement, vous ne pouvez pas appeler une table, mais avec des métatables, vous pouvez :
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Ajoute l'argument (5) à la valeur, puis le place dans la nouvelle table (t).
end
return unpack(sum) -- Retourne les valeurs individuelles de la table
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
Vous pouvez également faire beaucoup d'autres choses, comme additionner des tables :
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
Cela générera une erreur disant que vous tentez d'effectuer une arithmétique sur une table, mais cela fonctionne lorsqu'on tente avec une métatable :
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
Lors de l'utilisation de métatables, vous pouvez rencontrer quelques problèmes. Si vous devez utiliser la métaméthode __index pour créer de nouvelles valeurs dans une table, mais que la métatable de cette table a également une métaméthode __newindex, vous voudrez utiliser la fonction intégrée de Luau rawset() pour définir la valeur sans invoquer les métaméthodes. Prenez le code suivant comme exemple de ce qui se passe si vous n'utilisez pas ces fonctions.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- Ne définissez pas les valeurs dans la table de la manière habituelle
end
})
print(t[1]) -- Provoque un débordement de pile
Les débordements de pile se produisent lorsque vous essayez d'appeler une fonction depuis elle-même trop de fois. Dans la fonction __index ci-dessus, self[i] est défini sur une valeur, donc lorsqu'elle atteint la ligne suivante, self[i] devrait exister et ne devrait apparemment pas appeler la métaméthode __index. Le problème est que __newindex ne vous permet pas de définir la valeur. Sa présence empêche les valeurs d'être ajoutées à la table avec la méthode standard t[i] = v. Pour y remédier, utilisez la fonction rawset() :
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- Ne définissez pas les valeurs dans la table de la manière habituelle
end
})
print(t[1]) --> 10
Utilisez le type de données ensemble
Un ensemble est une collection d'éléments sans ordre et sans éléments dupliqués. Un élément est ou n'est pas contenu dans un ensemble. En utilisant des métatables, vous pouvez construire et manipuler des ensembles dans des scripts Luau.
Méthodes de base
Le code suivant inclut les fonctionnalités de base des ensembles, vous permettant de construire de nouveaux ensembles, d'ajouter et de supprimer un élément, de vérifier si un ensemble contient un élément et d'afficher le contenu d'un ensemble.
local Set = {}
Set.__index = Set
-- Fonction pour construire un ensemble à partir d'une liste optionnelle d'éléments
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Fonction pour ajouter un élément à un ensemble
function Set:add(item)
self[item] = true
end
-- Fonction pour supprimer un élément d'un ensemble
function Set:remove(item)
self[item] = nil
end
-- Fonction pour vérifier si un ensemble contient un élément
function Set:contains(item)
return self[item] == true
end
-- Fonction pour afficher l'ensemble en tant que liste délimitée par des virgules pour le débogage
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
Créer un ensemble
Un nouvel ensemble peut être construit en appelant Set.new() avec un tableau optionnel d'éléments à ajouter.
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})
Notez qu'en définition, un ensemble n'a pas de concept d'ordre.
Ajouter un élément
Ajouter un élément à un ensemble existant peut se faire via la méthode Set:add().
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})fruits:add("Mangue")
Supprimer un élément
Pour supprimer un élément d'un ensemble, appelez Set:remove() avec le nom de l'élément.
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})fruits:remove("Orange")
Vérifier la présence d'un élément
Pour vérifier si un ensemble contient un élément spécifique, utilisez Set:contains().
local fruits = Set.new({"Pomme", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})local result1 = fruits:contains("Cerise")print(result1) -- truelocal result2 = fruits:contains("Pastèque")print(result2) -- false
Méthodes supplémentaires
D'autres opérations utiles peuvent être mises en œuvre pour les ensembles, vous permettant de comparer des éléments entre des ensembles, de combiner des ensembles ou de soustraire un ensemble d'un autre.
Intersection
En considérant les ensembles comme des diagrammes de Venn, vous pouvez obtenir l'intersection de deux ensembles comme suit, c'est-à-dire les éléments qui apparaissent dans les deux ensembles.
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({"Mangue", "Citron", "Orange", "Cerise", "Citron vert", "Pêche"})
local frozenFruits = Set.new({"Mangue", "Pêche", "Ananas"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mangue, Pêche
Union
Vous pouvez obtenir l'union de deux ensembles avec la fonction suivante, c'est-à-dire une collection des éléments dans les deux ensembles sans doublons. Notez que cette fonction utilise la méthode de métatable __add pour fournir un raccourci d'addition de 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({"Pomme", "Mangue", "Cerise", "Pêche"})
local sourFruits = Set.new({"Citron", "Citron vert"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Pêche, Citron vert, Pomme, Cerise, Citron, Mangue
Soustraction
Vous pouvez retirer tous les éléments d'un ensemble des éléments d'un autre ensemble via la fonction suivante. Comme dans la fonction ci-dessus, cela utilise la méthode de métatable __sub pour fournir un raccourci de soustraction de 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({"Pomme", "Citron", "Mangue", "Cerise", "Citron vert", "Pêche"})
local sourFruits = Set.new({"Citron", "Citron vert"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mangue, Pomme, Cerise, Pêche