Lua Metatables
In a Lua table, we can access the corresponding key to get the value, but we cannot perform operations (like addition) on two tables.
Therefore, Lua provides metatables, which allow us to change the behavior of tables. Each behavior is associated with a corresponding metamethod.
For example, using a metatable, we can define how Lua calculates the addition operation `a+b` for two tables.
When Lua attempts to add two tables, it first checks if either has a metatable, and then checks if there is a field named `__add`. If found, it calls the corresponding value. Fields like `__add` are called "metamethods," and their corresponding values (often a function or a table) are the "metamethod implementations."
There are two important functions for handling metatables:
* **setmetatable(table, metatable):** Sets the metatable for the specified table. If the metatable contains a `__metatable` key, `setmetatable` will fail.
* **getmetatable(table):** Returns the metatable of an object.
The following example demonstrates how to set a metatable for a specified table:
```lua
mytable = {} -- normal table
mymetatable = {} -- metatable
setmetatable(mytable, mymetatable) -- set mymetatable as the metatable of mytable
The above code can also be written in one line:
```lua
mytable = setmetatable({}, {})
Here is how to return an object's metatable:
```lua
getmetatable(mytable) -- this will return mymetatable
* * *
## The `__index` Metamethod
This is the most commonly used key in metatables.
When you access a table by key, if that key has no value, Lua will look for the `__index` key in the table's metatable (assuming it has one). If `__index` contains a table, Lua will look up the corresponding key in that table.
We can observe this by entering interactive mode with the `lua` command:
```shell
$ 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, passing the table and the key as arguments.
The `__index` metamethod checks if an element exists in the table. If it does not exist, the result is `nil`; if it does exist, the result is returned by `__index`.
## Example
```lua
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 example output is:
value1 metatablevalue
Example analysis:
* The `mytable` table is assigned **{key1 = "value1"}**.
* `mytable` is set with a metatable, and the metamethod is `__index`.
* Look for `key1` in the `mytable` table. If found, return that element; if not, continue.
* Look for `key2` in the `mytable` table. If found, return `metatablevalue`; if not, continue.
* Check if the metatable has an `__index` method. If the `__index` method is a function, call that function.
* In the metamethod, check if the parameter "key2" is passed (`mytable.key2` is set). If the "key2" parameter is passed, return "metatablevalue"; otherwise, return the corresponding key-value from `mytable`.
We can simplify the above code as follows:
```lua
mytable = setmetatable({key1 = "value1"}, {__index = {key2 = "metatablevalue"}})
print(mytable.key1, mytable.key2)
> ### Summary
>
> The rules for Lua to look up a table element are actually the following 3 steps:
>
> * 1. Look up in the table. If found, return that element; if not, continue.
> * 2. Check if the table has a metatable. If there is no metatable, return `nil`; if there is a metatable, continue.
> * 3. Check if the metatable has an `__index` method. If the `__index` method is `nil`, return `nil`; if the `__index` method is a table, repeat steps 1, 2, 3; if the `__index` method is a function, return the function's return value.
>
> This part is from the author Huanzi: https://blog.csdn.net/xocoder/article/details/9028347
* * *
## The `__newindex` Metamethod
The `__newindex` metamethod is used to update a table, while `__index` is used to access a table.
When you assign a value to a missing index in a table, the interpreter will look for the `__newindex` metamethod: if it exists, it calls this function instead of performing the assignment.
The following example demonstrates the application of the `__newindex` metamethod:
## Example
```lua
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 the above example is:
value1 nil new value 2 new value 1 nil
In the above example, the table is set with the `__newindex` metamethod. When assigning a value to a new index key (`newkey`) (`mytable.newkey = "new value 2"`), the metamethod is called instead of performing the assignment. However, if assigning to an existing index key (`key1`), the assignment is performed, and the `__newindex` metamethod is not called.
The following example uses the `rawset` function to update a table:
## Example
```lua
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 the above example is:
new value "4"
* * *
## Adding Operations to Tables
The following example demonstrates the addition operation for two tables:
## Example
```lua
-- Custom function table_maxn to calculate the maximum key value in a table
function table_maxn(t)
local mn = 0
for k, _ in pairs(t) do
if type(k) == "number" and k > mn then
mn = k
end
end
return mn
end
-- Addition operation for two tables
mytable = setmetatable({1, 2, 3}, {
__add = function(mytable, newtable)
local max_key_mytable = table_maxn(mytable)
for i = 1, table_maxn(newtable) do
table.insert(mytable, max_key_mytable + i, newtable)
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 the above example is:
1 1
2 2
3 3
4 4
5 5
6 6
The `__add` key is contained in the metatable and performs the addition operation. The corresponding operation list in the table is as follows: (**Note:** `__` is two underscores)
| Pattern | Description |
| --- | --- |
| `__add` | Corresponds to the '+' operator. |
| `__sub` | Corresponds to the '-' operator. |
| `__mul` | Corresponds to the '*' operator. |
| `__div` | Corresponds to the '/' operator. |
| `__mod` | Corresponds to the '%' operator. |
| `__unm` | Corresponds to the '-' (unary) operator. |
| `__concat` | Corresponds to the '..' operator. |
| `__eq` | Corresponds to the '==' operator. |
| `__lt` | Corresponds to the '<' operator. |
| `__le` | Corresponds to the '<=' operator. |
* * *
## The `__call` Metamethod
The `__call` metamethod is called when Lua calls a value. The following example demonstrates calculating the sum of elements in a table:
## Example
```lua
-- Calculate the maximum value in a table. table.maxn is not available in Lua 5.2 and above.
-- Custom function table_maxn to calculate the number of elements in a table.
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
end
for i = 1, table_maxn(newtable) do
sum = sum + newtable
end
return sum
end
})
newtable = {10, 20, 30}
print(mytable(newtable))
The output of the above example is:
70
* * *
## The `__tostring` Metamethod
The `__tostring` metamethod is used to modify the output behavior of a table. In the following example, we customize the output content of a table:
## Example
```lua
mytable = setmetatable({10, 20, 30}, {
__tostring = function(mytable)
sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "The sum of all elements in the table is " .. sum
end
})
print(mytable)
The output of the above example is:
The sum of all elements in the table is 60
From this article, we can see that metatables can greatly simplify our code functionality. Therefore, understanding Lua's metatables can help us write simpler and better Lua code.
YouTip