Las metatablas permiten que las tablas sean más potentes que antes. Se adjuntan a los datos y contienen valores llamados metamétodos. Los metamétodos se ejecutan cuando se utiliza cierta acción con el dato al que están adjuntos.
Manipular metatablas
Las dos funciones principales para añadir y encontrar el metatable de una tabla son setmetatable() y getmetatable().
local x = {}local metaTable = {} -- ¡Las metatablas también son tablas!setmetatable(x, metaTable) -- ¡Dale a x una metatable llamada metaTable!print(getmetatable(x)) --> table: [dirección de memoria hexadecimal]
La función setmetatable() también devuelve la tabla a la que le estás estableciendo el metatable, por lo que estos dos scripts hacen lo mismo:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
Metamétodos
Los metamétodos son las funciones que se almacenan dentro de una metatable. Pueden ir desde llamar a una tabla, hasta añadir una tabla, e incluso dividir tablas también. Aquí está la lista de metamétodos disponibles:
| Método | Descripción |
|---|---|
| __index(table, index) | Se ejecuta cuando table[index] se indexa, si table[index] es nil. También se puede establecer en una tabla, en cuyo caso esa tabla se indexará. |
| __newindex(table, index, value) | Se ejecuta cuando se intenta establecer table[index] (table[index] = value), si table[index] es nil. También se puede establecer en una tabla, en cuyo caso esa tabla se indexará. |
| __call(table, ...) | Se ejecuta cuando la tabla se llama como una función, ... son los argumentos que fueron pasados. |
| __concat(table, value) | Se ejecuta cuando se utiliza el operador de concatenación .. en la tabla. |
| __unm(table) | Se ejecuta cuando se utiliza el operador unario – en la tabla. |
| __add(table, value) | El operador de adición +. |
| __sub(table, value) | El operador de sustracción –. |
| __mul(table, value) | El operador de multiplicación *. |
| __div(table, value) | El operador de división /. |
| __idiv(table, value) | El operador de división entera //. |
| __mod(table, value) | El operador de módulo %. |
| __pow(table, value) | El operador de potenciación ^. |
| __tostring(table) | Se ejecuta cuando se llama a tostring en la tabla. |
| __metatable | Si está presente, bloquea la metatable para que getmetatable() devuelva esto en lugar de la metatable y setmetatable() dará un error. Valor no funcional. |
| __eq(table, value) | El operador de igualdad ==¹ |
| __lt(table, value) | El operador de menos que <¹ |
| __le(table, value) | El operador de menor o igual que <=¹ |
| __mode | Utilizado en tablas débiles, declarando si las claves y/o valores de una tabla son débiles. Tenga en cuenta que las referencias a instancias de Roblox nunca son débiles. Las tablas que contienen tales referencias nunca serán recolectadas por el garbage collector. |
| __len(table) | Se ejecuta cuando se usa el operador de longitud # en el objeto. |
| __iter(table) | Se utiliza para denotar un iterador personalizado al usar iteración generalizada. |
Se debe tener en cuenta que, al escribir funciones para metamétodos aritméticos o relacionales, los dos parámetros de función son intercambiables entre la tabla que ejecutó el metamétodo y el otro valor. Por ejemplo, al hacer operaciones vectoriales con escalares, la división no es conmutativa. Por lo tanto, si estuvieras escribiendo metamétodos para tu propia clase vector2, querrías tener cuidado de tener en cuenta ambos escenarios.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a es un escalar, b es un vector
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a es un vector, b es un escalar
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
-- tanto a como b son vectores
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
Uso
Hay muchas formas de usar metatablas, por ejemplo, el metamétodo __unm para hacer que una tabla sea negativa:
local metatable = {
__unm = function(t) -- __unm es para el operador unario -
local negated = {}
for key, value in t do
negated[key] = -value -- negar todos los valores de esta tabla
end
return negated -- devolver la tabla
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
Aquí hay una forma interesante de declarar cosas usando __index:
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
__index se ejecutó cuando se indexó x en la tabla y no se encontró. Luau luego buscó a través de la tabla __index para un índice llamado x, y, al encontrar uno, lo devolvió.
Ahora puedes hacer esto fácilmente con una función simple, pero hay mucho más de donde vino eso. Toma este ejemplo:
local t = {10, 20, 30}print(t(5))
Normalmente no puedes llamar a una tabla, pero con metatablas puedes hacerlo:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Añade el argumento (5) al valor, luego colócalo en la nueva tabla (t).
end
return unpack(sum) -- Devuelve los valores individuales de la tabla
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
Puedes hacer mucho más también, como añadir tablas:
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
Esto dará un error diciendo que estás intentando realizar aritmética en una tabla, pero funciona cuando se intenta con una metatable:
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
Al jugar con metatablas, puedes encontrarte con algunos problemas. Si necesitas usar el metamétodo __index para crear nuevos valores en una tabla, pero la metatable de esa tabla también tiene un metamétodo __newindex, querrás usar la función incorporada de Luau rawset() para establecer el valor sin invocar ningún metamétodo. Toma el siguiente código como un ejemplo de lo que sucede si no usas estas funciones.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- No establezcas valores en la tabla de la manera normal
end
})
print(t[1]) -- Causa un desbordamiento de pila
Los desbordamientos de pila ocurren cuando intentas llamar a una función desde sí misma demasiadas veces. En la función __index anterior, self[i] se establece en un valor, por lo que cuando llega a la siguiente línea, self[i] debería existir y presumiblemente no llamará al metamétodo __index. El problema es que __newindex no te permite establecer el valor. Su presencia detiene los valores de ser añadidos a la tabla con el estándar t[i] = v método. Para superar esto, utiliza la función rawset():
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- No establezcas valores en la tabla de la manera normal
end
})
print(t[1]) --> 10
Usar el tipo de dato conjunto
Un conjunto es una colección de elementos sin orden y sin elementos duplicados. Un elemento es o no es contenido dentro de un conjunto. Usando metatablas, puedes construir y manipular conjuntos dentro de scripts de Luau.
Métodos básicos
El siguiente código incluye funcionalidad básica de conjunto, permitiéndote construir nuevos conjuntos, añadir y eliminar un elemento, verificar si un conjunto contiene un elemento y mostrar el contenido de un conjunto.
local Set = {}
Set.__index = Set
-- Función para construir un conjunto a partir de una lista opcional de elementos
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Función para añadir un elemento a un conjunto
function Set:add(item)
self[item] = true
end
-- Función para eliminar un elemento de un conjunto
function Set:remove(item)
self[item] = nil
end
-- Función para verificar si un conjunto contiene un elemento
function Set:contains(item)
return self[item] == true
end
-- Función para mostrar el conjunto como una lista separada por comas para depuración
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
Crear conjunto
Un nuevo conjunto se puede construir llamando a Set.new() con un array opcional de elementos para añadir.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
Ten en cuenta que, por definición, un conjunto no tiene concepto de orden.
Añadir elemento
Añadir un elemento a un conjunto existente se puede hacer a través del método Set:add().
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:add("Mango")
Eliminar elemento
Para eliminar un elemento de un conjunto, llama a Set:remove() con el nombre del elemento.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:remove("Orange")
Verificar elemento
Para verificar si un conjunto contiene un elemento específico, utiliza 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
Métodos adicionales
Otras operaciones útiles se pueden implementar para conjuntos, permitiéndote comparar elementos entre conjuntos, combinar conjuntos o restar un conjunto de otro.
Intersección
Al considerar conjuntos como diagramas de Venn, puedes obtener la intersección de dos conjuntos de la siguiente manera, lo que significa los elementos que aparecen en ambos conjuntos.
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
Unión
Puedes obtener la unión de dos conjuntos con la siguiente función, lo que significa una colección de los elementos en ambos conjuntos sin duplicados. Ten en cuenta que esta función utiliza el método __add de la metatable para proporcionar un acceso abreviado de suma 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
Sustracción
Puedes eliminar todos los elementos de un conjunto de los elementos de otro conjunto a través de la siguiente función. Similar a la función anterior, esta utiliza el método __sub de la metatable para proporcionar un acceso abreviado de resta 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