Metatables permitem que as tabelas se tornem mais poderosas do que antes. Elas estão anexadas a dados e contêm valores chamados metamétodos. Metamétodos são ativados quando uma determinada ação é usada com o dado ao qual está anexado.
Manipular metatables
As duas funções principais para adicionar e encontrar a metatable de uma tabela são setmetatable() e getmetatable().
local x = {}local metaTable = {} -- Metatables também são tabelas!setmetatable(x, metaTable) -- Dê a x uma metatable chamada metaTable!print(getmetatable(x)) --> table: [endereço de memória hexadecimal]
A função setmetatable() também retorna a tabela da qual você está definindo a metatable, então esses dois scripts fazem a mesma coisa:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
Metamétodos
Metamétodos são as funções que estão armazenadas dentro de uma metatable. Eles podem ir de chamar uma tabela, a adicionar uma tabela, até mesmo dividir tabelas. Aqui está a lista de metamétodos disponíveis:
| Método | Descrição |
|---|---|
| __index(tabela, índice) | Ativado quando tabela[índice] é indexado, se tabela[índice] for nil. Também pode ser definido como uma tabela, caso em que essa tabela será indexada. |
| __newindex(tabela, índice, valor) | Ativado quando tabela[índice] tenta ser definido (tabela[índice] = valor), se tabela[índice] for nil. Também pode ser definido como uma tabela, caso em que essa tabela será indexada. |
| __call(tabela, ...) | Ativado quando a tabela é chamada como uma função, ... são os argumentos que foram passados. |
| __concat(tabela, valor) | Ativado quando o operador de concatenação .. é usado na tabela. |
| __unm(tabela) | Ativado quando o operador unário – é usado na tabela. |
| __add(tabela, valor) | O operador de adição +. |
| __sub(tabela, valor) | O operador de subtração –. |
| __mul(tabela, valor) | O operador de multiplicação *. |
| __div(tabela, valor) | O operador de divisão /. |
| __idiv(tabela, valor) | O operador de divisão inteira //. |
| __mod(tabela, valor) | O operador de módulo %. |
| __pow(tabela, valor) | O operador de exponenciação ^. |
| __tostring(tabela) | Ativado quando tostring é chamado na tabela. |
| __metatable | Se presente, bloqueia a metatable para que getmetatable() retorne isso em vez da metatable e setmetatable() gere um erro. Valor não-funcional. |
| __eq(tabela, valor) | O operador de igualdade ==¹ |
| __lt(tabela, valor) | O operador menor que <¹ |
| __le(tabela, valor) | O operador menor ou igual <=¹ |
| __mode | Usado em tabelas fracas, declarando se as chaves e/ou valores de uma tabela são fracas. Observe que referências a instâncias do Roblox nunca são fracas. Tabelas que mantêm tais referências nunca serão coletadas pelo lixo. |
| __len(tabela) | Ativado quando o operador de comprimento # é usado no objeto. |
| __iter(tabela) | Usado para denotar um iterador personalizado ao usar iteração generalizada. |
Deve-se notar que ao escrever funções para metamétodos aritméticos ou relacionais, os dois parâmetros da função são intercambiáveis entre a tabela que ativou o metamétodo e o outro valor. Por exemplo, ao fazer operações vetoriais com escalares, a divisão não é comutativa. Portanto, se você estivesse escrevendo metamétodos para sua própria classe vector2, você deveria ter cuidado para levar em conta ambos os cenários.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a é um escalar, b é um vetor
local escalar, vetor = a, b
return vector2.new(escalar / vetor.x, escalar / vetor.y)
elseif type(b) == "number" then
-- a é um vetor, b é um escalar
local vetor, escalar = a, b
return vector2.new(vetor.x / escalar, vetor.y / escalar)
elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
-- ambos a e b são vetores
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
Existem muitas maneiras de usar metatables, por exemplo, o metamétodo __unm para tornar uma tabela negativa:
local metatable = {
__unm = function(t) -- __unm é para o operador unário -
local negated = {}
for key, value in t do
negated[key] = -value -- nega todos os valores nesta tabela
end
return negated -- retorna a tabela
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
Aqui está uma maneira interessante de declarar coisas usando __index:
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
__index foi ativado quando x foi indexado na tabela e não encontrado. Luau então procurou na tabela __index um índice chamado x e, encontrando um, o retornou.
Agora você pode fazer isso facilmente com uma função simples, mas há muito mais além disso. Veja este exemplo:
local t = {10, 20, 30}print(t(5))
Normalmente você não pode chamar uma tabela, mas com metatables você pode:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Adiciona o argumento (5) ao valor, então coloca na nova tabela (t).
end
return unpack(sum) -- Retorna os valores individuais da tabela
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
Você pode fazer muito mais, como adicionar tabelas:
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
Isso irá gerar um erro dizendo que você está tentando realizar operações aritméticas em uma tabela, mas funciona quando tentado com uma 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
Ao trabalhar com metatables, você pode encontrar alguns problemas. Se você precisar usar o metamétodo __index para criar novos valores em uma tabela, mas a metatable dessa tabela também tiver um metamétodo __newindex, você vai querer usar a função embutida do Luau rawset() para definir o valor sem invocar nenhum metamétodo. Veja o seguinte código como um exemplo do que acontece se você não usar essas funções.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- Não defina valores para a tabela da maneira normal
end
})
print(t[1]) -- Causa um estouro de pilha
Estouros de pilha acontecem quando você tenta chamar uma função a partir de si mesma um número excessivo de vezes. Na função __index acima, self[i] é definido para um valor, então quando chega à próxima linha, self[i] deve existir e presumivelmente não chamará o metamétodo __index. O problema é que __newindex não permite que você defina o valor. Sua presença impede que valores sejam adicionados à tabela com o método padrão t[i] = v. Para superar isso, use a função rawset():
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- Não defina valores para a tabela da maneira normal
end
})
print(t[1]) --> 10
Use o tipo de dado Set
Um set é uma coleção de itens sem ordem e sem elementos duplicados. Um item é ou não é contido dentro de um conjunto. Usando metatables, você pode construir e manipular sets dentro de scripts Luau.
Métodos básicos
O código a seguir inclui funcionalidades básicas de set, permitindo que você construa novos sets, adicione e remova um item, verifique se um set contém um item e exiba o conteúdo de um set.
local Set = {}
Set.__index = Set
-- Função para construir um set a partir de uma lista opcional de itens
function Set.new(itens)
local newSet = {}
for key, value in itens or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Função para adicionar um item a um set
function Set:add(item)
self[item] = true
end
-- Função para remover um item de um set
function Set:remove(item)
self[item] = nil
end
-- Função para verificar se um set contém um item
function Set:contains(item)
return self[item] == true
end
-- Função para exibir o set como uma lista delimitada por vírgulas para depuração
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
Criar set
Um novo set pode ser construído chamando Set.new() com um array opcional de itens para adicionar.
local frutas = Set.new({"Maçã", "Limão", "Laranja", "Cereja", "Lima", "Pêssego"})
Observe que, por definição, um set não tem conceito de ordenação.
Adicionar item
Adicionar um item a um set existente pode ser feito através do método Set:add().
local frutas = Set.new({"Maçã", "Limão", "Laranja", "Cereja", "Lima", "Pêssego"})frutas:add("Manga")
Remover item
Para remover um item de um set, chame Set:remove() com o nome do item.
local frutas = Set.new({"Maçã", "Limão", "Laranja", "Cereja", "Lima", "Pêssego"})frutas:remove("Laranja")
Verificar item
Para verificar se um set contém um item específico, use Set:contains().
local frutas = Set.new({"Maçã", "Limão", "Laranja", "Cereja", "Lima", "Pêssego"})local resultado1 = frutas:contains("Cereja")print(resultado1) -- truelocal resultado2 = frutas:contains("Melancia")print(resultado2) -- false
Métodos adicionais
Outras operações úteis podem ser implementadas para sets, permitindo que você compare itens entre sets, combine sets ou subtraia um set de outro.
Interseção
Ao considerar sets como diagramas de Venn, você pode obter a interseção de dois sets da seguinte forma, significando os itens que aparecem em ambos os sets.
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 frutasFrescas = Set.new({"Manga", "Limão", "Laranja", "Cereja", "Lima", "Pêssego"})
local frutasCongeladas = Set.new({"Manga", "Pêssego", "Abacaxi"})
local frutasComuns = getIntersection(frutasFrescas, frutasCongeladas)
frutasComuns:output() -- Manga, Pêssego
União
Você pode obter a união de dois sets com a seguinte função, significando uma coleção dos itens em ambos os sets sem duplicados. Observe que essa função utiliza o método __add da metatable para fornecer um atalho de adição set1 + set2.
function Set:__add(outroSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in outroSet do
result[entry] = true
end
return result
end
local frutasDoces = Set.new({"Maçã", "Manga", "Cereja", "Pêssego"})
local frutasAzedas = Set.new({"Limão", "Lima"})
local todasFrutas = frutasDoces + frutasAzedas
todasFrutas:output() -- Pêssego, Lima, Maçã, Cereja, Limão, Manga
Subtração
Você pode remover todos os itens em um set dos itens em outro set através da seguinte função. Semelhante à função acima, essa utiliza o método __sub da metatable para fornecer um atalho de subtração set1 - set2.
function Set:__sub(outroSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in outroSet do
result[entry] = nil
end
return result
end
local todasFrutas = Set.new({"Maçã", "Limão", "Manga", "Cereja", "Lima", "Pêssego"})
local frutasAzedas = Set.new({"Limão", "Lima"})
local frutasDoces = todasFrutas - frutasAzedas
frutasDoces:output() -- Manga, Maçã, Cereja, Pêssego