Las metatables permiten que las tablas se vuelvan más poderosas que antes. Están conectadas a los datos y contienen valores llamados metam métodos. Los métodos metam se activan cuando se usa una determinada acción con el datum a la que está conectada.
Considera el siguiente código:
local list = {1, 2}print(list[3])
Puede esperar que este código busque en la lista el índice de tercer lugar, encuentre nada y luego regrese nulo. Eso no es correcto, sin embargo. Lo que realmente sucede es que el código busca a través de la lista para el tercer índice, encuentra nada y luego comprueba si hay un metatable adjunto a la tabla, que luego devuelve nil si no hay uno.
Manipulando Metatables
Las dos funciones principales para agregar y encontrar una tabla metatable, son setmetatable y getmetatable
local x = {}local metaTable = {} -- ¡Las metaTables son tablas también!setmetatable(x, metaTable) -- ¡Dale x a una tabla de metadatos llamada metaTable!print(getmetatable(x)) --> table: [hexadecimal memory address]
La función setmetatable también devuelve la tabla que estás configurando, para que estos dos scripts hagan lo mismo:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
Metamétodos
Los metamétodos son las funciones que se almacenan dentro de una metaverificación. Pueden ir desde llamar una tabla, hasta agregar una tabla, incluso dividir tablas. Aquí está la lista de metamétodos disponibles:
Método | Descripción |
---|---|
__index(tabla, index) | Se activa cuando la tabla [index] está indexada, si la tabla [index] es nula. También se puede configurar para una tabla, en cuyo caso esa tabla se indexará. |
__newindex(tabla, índice, valor) | Se activa cuando la tabla [index] intenta ser establecida (tabla [index] = valor), si la tabla [index] es nula. También se puede establecer a una tabla, en cuyo caso esa tabla se indexará. |
__call(tabla, ...) | Se activa cuando la tabla se llama como una función, ... son los argumentos que se pasaron. |
__concat(valor, tabla) | Se activa cuando se usa el operador de concatenación .. en la tabla. |
__unm(mesa) | Se activa cuando el operador unario - se usa en la tabla. |
__add(valor, tabla) | El operador de la suma. |
__sub(mesa, valor) | El operador -subtracción. |
__mul(tabla, valor) | El operador de multiplicación * |
__div(valor, tabla) | El operador / división. |
__idiv(tabla, valor) | El // operador de división de piso. |
__mod(valor, tabla) | El operador modulo%. |
__pow(tabla, valor) | El operador ^ de expansión. |
__tostring(mesa) | Se ha disparado cuando se llama a tostring en la tabla. |
__metitable | Si se presenta, bloquea la tabla de metas para que getmetable devuelva esto en lugar de la tabla de metas y setmetable se errorará. No es un valor de función. |
__eq(mesa, valor) | El == es igual a operador¹ |
__lt(mesa, valor) | El < operador¹ menor |
__le(tabla, valor) | El operador <<operador¹ |
__modo | Se usa en tablas débiles, declarando si las llaves y/o valores de una tabla son débiles. Nota: Las referencias a instancias de Roblox nunca son débiles. Las tablas que contienen tales referencias nunca se reciclarán. |
__gc(mesa) | Se ha lanzado cuando se recoge el vertido. Nota: En Roblox, este método metamétrico está deshabilitado. |
__len(mesa) | Se ha disparado cuando el operador de longitud # se ha utilizado en el objeto. |
__iter(mesa) | Se usa para designar un innovador personalizado al usar la innovación generalizada. |
Se debe tener en cuenta que cuando se escriben funciones para ambos métodos aritméticos o relacionales, los dos parámetros de función son intercambiables entre la tabla que ejecutó el método y la otra valor. Por ejemplo, cuando se realizan operaciones de véctores con división de escalas no es común. Por lo tanto, si estaba escribiendo metamétodos para su propia clase de véctores2, debería tener cuidado para asegurar que se cuente con suficiente para ambos escenarios.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a es un valor de tipo escalar, b es un valor de tipo fuerza vectorial
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a es un fuerza vectorial, b es un scalar
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
-- ambos a y 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.333333333333, 1.25
print(b / a) -- 0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5
Usando Metatables
Hay muchas formas de usar metatables, por ejemplo el método __unm metamétodo (para hacer una tabla 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 en esta tabla
end
return negated -- 返回表
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
Aquí hay una manera interesante de declarar cosas usando __index :
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
__index fue lanzado cuando x se indexó en la tabla y no se encontró. Lua luego buscó a través de la tabla __index para un índice llamado x, y, encontrando uno, devolvió que.
Ahora puede hacer eso con una función simple, pero hay mucho más donde eso vino de. Toma esto por ejemplo:
local t = {10, 20, 30}print(t(5))
Ahora, obviamente, no puedes llamar a una tabla. Eso es solo loco, pero (¡sorpresa, sorpresa!) con metatables puedes.
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Agregue el argumento (5) a la valor, luego colóquelo en la nueva tabla (t).
end
return unpack(sum) -- Restablecer los valores de la tabla individual
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
¡También puedes hacer mucho más, como agregar tablas!
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
Esto fallará diciendo que estás tratando de realizar aritmética en una tabla. Vamos a intentar esto 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
Uso de casos
Ahora, todos estos ejemplos se pueden implementar como una función simple, pero puedes hacer mucho más que eso. Intenta un programa simple que memorizará un número cuando se ponga un problema matemático posiblemente laggy en él.
Para este usaremos el método metámetro __index para hacerlo simple:
local function mathProblem(num)
for i = 1, 20 do
num = math.floor(num * 10 + 65)
end
for i = 1, 10 do
num += i - 1
end
return num
end
local metatable = {
__index = function(object, key)
local num = mathProblem(key)
object[key] = num
return num
end
}
local t = setmetatable({}, metatable)
print(t[1]) -- Será lento porque es la primera vez que se usa este número, así que tiene que ejecutar la función de matemáticas.
print(t[2]) -- será lento porque es la primera vez que se usa este número.
print(t[1]) -- will be fast because it's just grabbing the number from the table.
Rawset, Rawget, Rawequal
Cuando juegas con metatables, puede que te enfrentes a algunos problemas. ¿Qué sucede si necesitas usar el __index metamétodo para crear nuevos valores en una tabla, pero que el metatable de esa tabla también tiene un método __newindex en él? Deberías usar la función de código básico raw
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10 -- solo como ejemplo
return self[i]
end,
__newindex = function(self, i, v)
--no hagas nada porque no queremos que establezcas los valores en la tabla de la manera normal
end
})
print(t[1]) -- Causes a C-Stack overflow
Ahora, ¿por qué esto causaría un error de pila? Los errores de pila ocurren cuando intentas llamar una función desde sí misma demasiadas veces, pero ¿qué podría causar que eso suceda? En la función __index, configuramos self[i] para un valor, así que cuando llega a la siguiente línea, self[i]
El problema es que __newindex no nos permite establecer el valor. Su presencia impide que se agreguen los valores a la tabla con el método estándar t[i] = v. Para superar esto, usa 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 hagas nada porque no queremos que establezcas los valores en la tabla de la manera normal
end
})
print(t[1]) -- prints 10
Usando el tipo de datos de la lista
Un conjunto es una colección de elementos sin orden y sin elementos duplicados. Un elemento es o no está contenido dentro de un establecer. Usando metatables, puede construir y manipular conjuntos dentro de los scripts Lua.
Métodos básicos
El siguiente código incluye funciones básicas de conjunto, lo que te permite construir nuevos conjuntos, agregar y eliminar un objeto, ver si un conjunto contiene un objetoy exportar los elementos de un establecer.
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 agregar un artículo a un establecer
function Set:add(item)
self[item] = true
end
-- Función para eliminar un elemento de un establecer
function Set:remove(item)
self[item] = nil
end
-- Función para verificar si un conjunto contiene un objeto
function Set:contains(item)
return self[item] == true
end
-- Función para generar un conjunto de funciones como una lista de comas para la depresió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
Se puede crear un nuevo conjunto al llamar Set.new() con un conjunto de opciones de elementos para añadir.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
Nota que por definición, un conjunto no tiene un concepto de orden.
Añadir artículo
Añadir un artículo 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 establecer, llama a Set:remove() con el nombre del elemento.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:remove("Orange")
Verificar por Artículo
Para verificar si un conjunto contiene un objetoespecífico, usa Set:contains() .
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})local result1 = fruits:contains("Cherry")print(result1) -- ciertolocal result2 = fruits:contains("Watermelon")print(result2) -- false
Métodos Adicionales
Otras operaciones útiles se pueden implementar para los conjuntos, lo que te permite comparar elementos entre conjuntos, combinar conjuntos o restar un conjunto de otro.
Intersección
Al considerar conjuntos como diagrama de Venn, puede obtener la intersección de dos conjuntos como sigue, lo que significa que 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 union de dos conjuntos con la siguiente función, lo que significa una colección de los elementos en ambos conjuntos sin duplicados. Nota que esta función usa el método __add para proporcionar una ruta de adición corta 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({"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 en un conjunto de los elementos en otro conjunto a través de la siguiente función. Esta función es similar a la función de arriba, que usa el método metatable __sub para proporcionar un atajo de subtracción 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({"Apple", "Lemon", "Mango", "Cherry", "Lime", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apple, Cherry, Peach