Le metatabelle consentono alle tabelle di diventare più potenti di prima. Sono collegate ai dati e contengono valori chiamati metametodi. I metametodi vengono attivati quando viene utilizzata una certa azione con il dato a cui sono collegati.
Manipolare le metatabelle
Le due funzioni principali per aggiungere e trovare la metatabella di una tabella sono setmetatable() e getmetatable().
local x = {}
local metaTable = {} -- Le metatabelle sono tabelle, anch'esse!
setmetatable(x, metaTable) -- Assegna a x una metatabella chiamata metaTable!
print(getmetatable(x)) --> table: [indirizzo di memoria esadecimale]La funzione setmetatable() restituisce anche la tabella a cui stai impostando la metatabella, quindi questi due script fanno la stessa cosa:
local x = {}
setmetatable(x, {})local x = setmetatable({}, {})Metametodi
I metametodi sono le funzioni che sono memorizzate all'interno di una metatabella. Possono andare dall'invocare una tabella, all'aggiungere una tabella, fino a dividere anche le tabelle. Ecco l'elenco dei metametodi disponibili:
| Metodo | Descrizione |
|---|---|
| __index(table, index) | Viene attivato quando table[index] viene indicizzato, se table[index] è nil. Può anche essere impostato su una tabella, nel qual caso quella tabella verrà indicizzata. |
| __newindex(table, index, value) | Viene attivato quando si cerca di impostare table[index] (table[index] = value), se table[index] è nil. Può anche essere impostato su una tabella, nel qual caso quella tabella verrà indicizzata. |
| __call(table, ...) | Viene attivato quando la tabella viene chiamata come una funzione, ... sono gli argomenti che sono stati passati. |
| __concat(table, value) | Viene attivato quando viene utilizzato l'operatore di concatenazione .. sulla tabella. |
| __unm(table) | Viene attivato quando viene utilizzato l'operatore unario – sulla tabella. |
| __add(table, value) | L'operatore di somma +. |
| __sub(table, value) | L'operatore di sottrazione –. |
| __mul(table, value) | L'operatore di moltiplicazione *. |
| __div(table, value) | L'operatore di divisione /. |
| __idiv(table, value) | L'operatore di divisione intera //. |
| __mod(table, value) | L'operatore di modulo %. |
| __pow(table, value) | L'operatore di esponenziazione ^. |
| __tostring(table) | Attivato quando viene chiamato tostring sulla tabella. |
| __metatable | Se presente, blocca la metatabella in modo che getmetatable() restituisca questa invece della metatabella e setmetatable() generi un errore. Valore non funzione. |
| __eq(table, value) | L'operatore di uguaglianza ==¹ |
| __lt(table, value) | L'operatore di minore <¹ |
| __le(table, value) | L'operatore di minore o uguale <=¹ |
| __mode | Utilizzato nelle tabelle deboli, dichiarando se le chiavi e/o i valori di una tabella sono deboli. Vedi Usa tabelle deboli. |
| __len(table) | Attivato quando viene utilizzato l'operatore di lunghezza # sull'oggetto. |
| __iter(table) | Utilizzato per denotare un iteratore personalizzato quando si utilizza l'iterazione generalizzata. |
Va notato che quando si scrivono funzioni per metametodi aritmetici o relazionali, i due parametri della funzione sono intercambiabili tra la tabella che ha attivato il metametodo e l'altro valore. Ad esempio, quando si eseguono operazioni vettoriali con scalari, la divisione non è commutativa. Pertanto, se stai scrivendo metametodi per la tua classe vector2, dovresti prestare attenzione a considerare entrambi gli scenari.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a è uno scalare, b è un vettore
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a è un vettore, b è uno scalare
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
-- sia a che b sono vettori
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.5Utilizzo
Ci sono molti modi per utilizzare le metatabelle.
Applicare l'operatore meno unario alle tabelle
Il metametodo __unm gestisce l'operatore meno unario. Usalo per negare ogni valore in una tabella:
local metatable = {
__unm = function(t) -- __unm è per l'operatore unario -
local negated = {}
for key, value in t do
negated[key] = -value -- nega tutti i valori in questa tabella
end
return negated -- restituisce la tabella
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12Indicizzare valori mancanti da un'altra tabella
Ecco un modo interessante per dichiarare cose utilizzando __index:
local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1__index è stato attivato quando x è stato indicizzato nella tabella e non trovato. Luau ha quindi cercato nella tabella __index un indice chiamato x, e, trovandone uno, lo ha restituito.
Chiamare una tabella come una funzione
Le metatabelle possono fare molto di più che fornire un valore mancante. Prendi ad esempio questo:
local t = {10, 20, 30}
print(t(5))Tipicamente non puoi chiamare una tabella, ma con le metatabelle puoi farlo:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Aggiungi l'argomento (5) al valore, poi mettilo nella nuova tabella (t).
end
return unpack(sum) -- Restituisci i valori della tabella individualmente
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35Applicare l'operatore di somma alle tabelle
Puoi fare molto di più, come sommare tabelle:
local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
endQuesto genererà un errore dicendo che stai tentando di eseguire un'operazione aritmetica su una tabella, ma funziona quando viene tentato con una metatabella:
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)
endImpostare valori senza attivare metametodi
Quando giochi con le metatabelle, potresti incontrare alcuni problemi. Se hai bisogno di utilizzare il metametodo __index per creare nuovi valori in una tabella, ma la metatabella di quella tabella ha anche un metametodo __newindex, vorrai utilizzare la funzione integrata di Luau rawset() per impostare il valore senza attivare alcun metametodo. Prendi il seguente codice come esempio di cosa succede se non utilizzi queste funzioni.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- Non impostare valori nella tabella nel modo normale
end
})
print(t[1]) -- Causa un overflow dello stackGli overflow dello stack si verificano quando cerchi di chiamare una funzione da se stessa troppe volte. Nella funzione __index sopra, self[i] viene impostato su un valore, quindi quando si arriva alla riga successiva, self[i] dovrebbe esistere e presumibilmente non attiverà il metametodo __index. Il problema è che __newindex non ti consente di impostare il valore. La sua presenza impedisce che i valori vengano aggiunti alla tabella con il metodo standard t[i] = v. Per superare questo, utilizza la funzione rawset():
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- Non impostare valori nella tabella nel modo normale
end
})
print(t[1]) --> 10Usa tabelle deboli
Le variabili che contengono tabelle, funzioni, thread e userdata, inclusi Instances, memorizzano riferimenti a quegli oggetti anziché copie di essi. Assegnare un oggetto a un'altra variabile crea un altro riferimento allo stesso oggetto:
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) --> trueGli oggetti che non sono più raggiungibili tramite riferimenti forti diventano idonei per la raccolta dei rifiuti. Impostare una variabile su nil non rende un oggetto idoneo per la raccolta dei rifiuti se un altro riferimento forte punta ancora a quell'oggetto:
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [indirizzo di memoria esadecimale]Sebbene child non faccia più riferimento alla tabella figlio, parent.Child lo fa ancora. Finché la tabella padre rimane raggiungibile, il suo riferimento mantiene la tabella figlio raggiungibile.
Una tabella debole contiene chiavi, valori o entrambi senza impedire al garbage collector di recuperare oggetti altrimenti irraggiungibili. Imposta il campo __mode della metatabella della tabella per controllare quali riferimenti sono deboli:
- "k" rende le chiavi deboli.
- "v" rende i valori deboli.
- "kv" o "vk" rende sia le chiavi che i valori deboli.
Il seguente esempio di codice crea una tabella con valori deboli:
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
endDopo che il blocco do termina, il valore memorizzato in children.example non ha riferimenti forti e diventa idoneo per la raccolta dei rifiuti. Quando il garbage collector recupera la tabella figlio, Luau rimuove la sua voce da children. La raccolta dei rifiuti è non deterministica, quindi non fare affidamento su quando l'elemento scompare.
Qualsiasi modalità che lasci fuori rimane forte. Le chiavi in una tabella __mode = "v" rimangono riferimenti forti, quindi un oggetto memorizzato come chiave rimane raggiungibile e la sua voce rimane nella tabella.
Per Instances, la raccolta dei rifiuti si applica al riferimento Luau piuttosto che all'oggetto stesso. Il motore memorizza i dati dell'istanza al di fuori della VM Luau, quindi raccogliere un riferimento Luau non elimina l'istanza, e un'istanza che rimane nel modello di dati non mantiene il suo riferimento Luau attivo. Per rilasciare un'istanza, chiama Instance:Destroy() e cancella ogni riferimento rimanente ad essa.
Usa il tipo di dato set
Un set è una collezione di elementi senza ordine e senza elementi duplicati. Un elemento è o non è contenuto all'interno di un set. Utilizzando le metatabelle, puoi costruire e manipolare set all'interno degli script Luau.
Metodi di base
Il seguente codice include funzionalità di base del set, consentendoti di costruire nuovi set, aggiungere e rimuovere un elemento, controllare se un set contiene un elemento e stampare il contenuto di un set.
local Set = {}
Set.__index = Set
-- Funzione per costruire un set da un elenco opzionale di elementi
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Funzione per aggiungere un elemento a un set
function Set:add(item)
self[item] = true
end
-- Funzione per rimuovere un elemento da un set
function Set:remove(item)
self[item] = nil
end
-- Funzione per controllare se un set contiene un elemento
function Set:contains(item)
return self[item] == true
end
-- Funzione per stampare il set come un elenco delimitato da virgole per il debug
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
endCrea set
Un nuovo set può essere costruito chiamando Set.new() con un array opzionale di elementi da aggiungere.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})Nota che per definizione, un set non ha concetto di ordinamento.
Aggiungi elemento
Aggiungere un elemento a un set esistente può essere fatto tramite il metodo Set:add().
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:add("Mango")Rimuovi elemento
Per rimuovere un elemento da un set, chiama Set:remove() con il nome dell'elemento.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:remove("Orange")Controlla per elemento
Per controllare se un set contiene un elemento specifico, usa 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) -- falseMetodi aggiuntivi
Altre operazioni utili possono essere implementate per i set, consentendoti di confrontare elementi tra set, combinare set o sottrarre un set da un altro.
Intersezione
Quando si considerano i set come diagrammi di Venn, puoi ottenere l'intersezione di due set come segue, significando gli elementi che appaiono in entrambi i set.
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, PeachUnione
Puoi ottenere l'unione di due set con la seguente funzione, significando una collezione degli elementi in entrambi i set senza duplicati. Nota che questa funzione utilizza il metodo __add della metatabella per fornire una scorciatoia di somma di 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, MangoSottrazione
Puoi rimuovere tutti gli elementi in un set dagli elementi in un altro set tramite la seguente funzione. Simile alla funzione sopra, questa utilizza il metodo __sub della metatabella per fornire una scorciatoia di sottrazione di 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