YouTip LogoYouTip

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.
← Lua LoopsLua Data Types β†’