Lua Metatables
In Lua, we can access the corresponding key to get the value in a table, but we cannot perform operations on two tables (such as addition).
Therefore, Lua provides metatables, which allow us to alter the behavior of tables, with each behavior associated with a corresponding metamethod.
For example, using metatables, we can define how Lua computes the addition of two tables, a + b.
When Lua attempts to add two tables, it first checks if either of them has a metatable and then checks if there is a field called __add. If found, it calls the corresponding value. Fields like __add are immediate fields, and their corresponding values (often functions or tables) are "metamethods".
There are two important functions for handling metatables:
- setmetatable(table, metatable): Sets the metatable for the specified table. If the metatable contains the key
__metatable, setmetatable will fail. - getmetatable(table): Returns the metatable of the object.
The following example demonstrates how to set a metatable for a specified table:
mytable = {} -- Ordinary table
mymetatable = {} -- Metatable
setmetatable(mytable, mymetatable) -- Set mymetatable as the metatable of mytable
The above code can also be written in one line:
mytable = setmetatable({}, {})
The following returns the object's metatable:
getmetatable(mytable) -- This will return mymetatable
__index Metamethod
This is the most commonly used key in metatables.
When you access a table by key, if this key has no value, Lua will look for the __index key in the table's metatable (assuming there is a metatable). If __index contains a table, Lua will look up the corresponding key in that table.
We can see this in action using the Lua interactive mode:
$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil
If __index contains a function, Lua will call that function, with the table and key as arguments.
The __index metamethod checks if an element exists in the table. If it does not exist, it returns nil; if it exists, it returns the result from __index.
Example
mytable = setmetatable({key1 = "value1"}, {
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1, mytable.key2)
The output of this example is:
value1 metatablevalue
Example Analysis:
mytableis assigned as{key1 = "value1"}.mytableis set with a metatable, with the metamethod__index.- When looking up
key1inmytable, if found, it returns the element; if not, it continues. - When looking up
key2inmytable, if found, it returnsmetatablevalue; if not, it continues. - It checks if the metatable has an
__indexmethod. If__indexis a function, it calls this function. - The metamethod checks if the parameter "key2" is passed (since
mytable.key2is set). If "key2" is passed, it returns "metatablevalue"; otherwise, it returns the corresponding value frommytable.
We can simplify the above code to:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1, mytable.key2)
Summary
The rules Lua uses to look up a table element are as follows:
- Look in the table. If found, return the element; if not, continue.
- Check if the table has a metatable. If no metatable, return nil; if there is a metatable, continue.
- Check if the metatable has an
__indexmethod. If__indexis nil, return nil; if__indexis a table, repeat steps 1, 2, 3; if__indexis a function, return the function's return value.
This content is from the author Huanzi: https://blog.csdn.net/xocoder/article/details/9028347
__newindex Metamethod
The __newindex metamethod is used for updating tables, while __index is used for accessing tables.
When you assign a value to a missing index in a table, the interpreter looks for the __newindex metamethod: if found, it calls this function instead of performing the assignment.
The following example demonstrates the use of the __newindex metamethod:
Example
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
print(mytable.key1)
mytable.newkey = "New Value 2"
print(mytable.newkey, mymetatable.newkey)
mytable.key1 = "New Value 1"
print(mytable.key1, mymetatable.key1)
The output of this example is:
value1
nil New Value 2
New Value 1 nil
In this example, the table has the __newindex metamethod set. When assigning to a new index key (mytable.newkey = "New Value 2"), it calls the metamethod and does not perform the assignment. However, if assigning to an existing index key (key1), it performs the assignment without calling the __newindex metamethod.
The following example uses the rawset function to update the table:
Example
mytable = setmetatable({key1 = "value1"}, {
__newindex = function(mytable, key, value)
rawset(mytable, key, "\"" .. value .. "\"")
end
})
mytable.key1 = "new value"
mytable.key2 = 4
print(mytable.key1, mytable.key2)
The output of this example is:
new value "4"
Adding Operators to Tables
The following example demonstrates adding two tables:
Example
-- Function to calculate the maximum value in a table, as table.maxn is deprecated in Lua 5.2+
function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- Adding two tables
mytable = setmetatable({ 1, 2, 3 }, {
__add = function(mytable, newtable)
for i = 1, table_maxn(newtable) do
table.insert(mytable, table_maxn(mytable) + 1, newtable[i])
end
return mytable
end
})
secondtable = {4, 5, 6}
mytable = mytable + secondtable
for k, v in ipairs(mytable) do
print(k, v)
end
The output of this example is:
1 1
2 2
3 3
4 4
5 5
6 6
The __add key is included in the metatable for addition operations. The corresponding operations for tables are as follows (Note: __ is two underscores):
| Mode | Description |
|---|---|
| __add | Corresponding operator '+'. |
| __sub | Corresponding operator '-'. |
| __mul | Corresponding operator '*'. |
| __div | Corresponding operator '/'. |
| __mod | Corresponding operator '%'. |
| __unm | Corresponding operator '-'. |
| __concat | Corresponding operator '..'. |
| __eq | Corresponding operator '=='. |
| __lt | Corresponding operator '<'. |
| __le | Corresponding operator '<='. |
__call Metamethod
The __call metamethod is invoked when Lua calls a value. The following example demonstrates calculating the sum of elements in a table:
Example
-- Function to calculate the maximum value in a table, as table.maxn is deprecated in Lua 5.2+
function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- Define the __call metamethod
mytable = setmetatable({10}, {
__call = function(mytable, newtable)
sum = 0
for i = 1, table_maxn(mytable) do
sum = sum + mytable[i]
end
for i = 1, table_maxn(newtable) do
sum = sum + newtable[i]
end
return sum
end
})
newtable = {10, 20, 30}
print(mytable(newtable))
The output of this example is:
70
__tostring Metamethod
The __tostring metamethod is used to modify the output behavior of a table. The following example customizes the output of a table:
Example
mytable = setmetatable({ 10, 20, 30 }, {
__tostring = function(mytable)
sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "Sum of table elements is " .. sum
end
})
print(mytable)
The output of this example is:
Sum of table elements is 60
Understanding metatables can greatly simplify our code and allow us to write more elegant Lua code.