Revision Difference
Metamethods#528210
<cat>Dev.Lua</cat>
# What is a Metamethod?
Metamethods are special functions that allow you to override specific operations performed on Lua tables.
When a table of metamethods is applied to a table, it is then known as its <page>Meta Tables</page>.
When used correctly, they can allow tables to behave in very unique and complex ways that would not otherwise be possible.
They are also the powerhouse behind object oriented programming in lua.
You can live a fulfilling life in Lua without metatables, but hopefully with a practical example you will see their usefulness.
Shown here is how some metamethods of a metatable fit together.
```
local meta = {}
-- When you call the table like a function ( something() ), the metamethod
-- __call will be called (hence its name) with the first argument being the
-- table (shown with "self") and the arguments that were passed.
function meta.__call( self, var )
self.myvar = var
return var + self:Cheese()
end
local metaIndex = {}
-- __index could either be a table or a function, in this example it is a function.
-- __index allows you to call functions that are otherwise not visible to the regular table.
-- The "Cheese" metamethod is a simple metamethod that just returns the variable "myvar" that exists in the actual table.
-- Keep in mind, if you are calling a method in __index you will need to pass the table as the first argument or use
-- a colon when calling the function.
function metaIndex.Cheese( self )
return self.myvar
end
meta.__index = metaIndex
local myObject = {}
setmetatable( myObject, meta ) -- Setting up the metatable.
print( myObject( 5 ) ) -- 10
print( myObject.myvar ) -- 5
-- Even if you set the variable manually, the Cheese metamethod will still output the value of the variable.
myObject.myvar = 3
print( myObject:Cheese() ) -- 3
```
# Notable Uses
Perhaps the most useful of the metamethods are the __call and __index entries.
### __call
__call allows tables that have a metatable to be called like a function, with the table being passed as the first argument.
```
local meta = {}
-- This is the same as function meta:__call( key )
function meta.__call( self )
self.x = ( self.x or 0 ) + 1
return self.x
end
local tbl = setmetatable( { x = 5 }, mymeta )
local tbl = setmetatable( { x = 5 }, meta )
print( tbl.x ) -- 5
print( tbl() ) -- 6
print( tbl.__call ) -- nil
```
### __index
__index is far more interesting and is what gives metamethods most of their power.
It can either be a table, or a function.
If it's a table, the object will lookup entries in it for the specified key.
This is what allows functions defined in the meta.__index table to be called on the object table.
Refer to the "metaIndex" variable written in the first example.
If this index table also has its own metatable and does not find the key, then this new metatable will be used and it will follow the chain up until it reaches a return value or the end of the index chain, whichever comes first.
Here is an example of what this looks like.
```
local metaApple = { apple = "apples" }
local metaPear = { pear = "pears" }
local metaOrange = { orange = "oranges" }
setmetatable( metaApple, { __index = metaPear } )
setmetatable( metaPear, { __index = metaOrange } )
local mytbl = {}
setmetatable( mytbl, { __index = metaApple } )
print( mytbl.orange ) -- "oranges"
```
Used correctly this will achieve outcomes very similar to how entity .Base inheritence works.
And if it's a function, it will call the function and provide the returned value.
This example is the simplest way to give a table a "default value".
```
local meta = {}
-- This is the same as function meta:__index( key )
function meta.__index( self, key )
return "cheese"
end
local tbl = setmetatable( { apple = "Apples" }, mymeta )
local tbl = setmetatable( { apple = "Apples" }, meta )
print( tbl.apple ) -- "Apples"
print( tbl.orange ) -- "cheese"
```
<note>Trying to index a table from within its own index function can result in infinite looping if not written correctly. See also <page>Global.rawget</page> and <page>Global.rawset</page> which will act upon tables without calling metamethods such as __index and __newindex in their metatable, which can be used to prevent infinite looping.</note>
It's possible to write metatables much smaller than what is shown here, and with less complexity by setting the metatable __index entry to reference the metatable. Example:
```
local meta = {}
meta.__index = meta
-- This is the same as function meta.Cheese( self )
function meta:Cheese()
print(self.myvar)
end
local mytbl = {}
setmetatable( mytbl, meta )
mytbl.myvar = "Kittens"
mytbl:Cheese() -- "Kittens"
```
There are many different ways to write metatables, and it all comes down to what you'll be using it for and what you need Lua objects to do.
# A list of all the Metamethods
All metamethod names begin with __, and are named logically (e.g. __add corresponds to the + operator).
see [this page](http://www.tutorialspoint.com/lua/lua_metatables.htm) for more thorough descriptions of each metamethod.
# Learn By Example
One common programming task is to compare one item against many. For instance, when we want to check whether a player has said a keyword, you could use table.HasValue
```
local fruits = { "banana", "apple", "strawberry", "orange" } -- Not limited to just these items.
local word = "orange"
if table.HasValue( fruits, word ) then
print( "The word is a fruit!" )
end
```
The issue with this approach is that HasValue is essentially a for loop checking if the fruit variable is equal to the word variable. With large tables, if you check HasValue frequently enough it will really slow down your program! Instead, we can make this more efficient by doing the following.
```
local hasFruit = {
banana = true,
apple = true,
orange = true
} -- plus 2000 other fruits
local word = "orange"
if hasFruit[ word ] then
print( "The word is a fruit!" )
end
```
This solution is pretty elegant. hasFruit[...] will return true if the word is in our table, and nil otherwise.
# Metatable Example
An example that puts multiple metamethods to use is this Set script, which shows the possibilities of what you could do with metamethods.
You can easily run the following code by creating a custom .lua script in the lua/autorun folder. Happy learning!
```
Set = {} -- This metatable will hold both metamethods and normal methods
-- Set.new is just the function 'new' in the 'Set' table, nothing magical.
-- This method makes our lives easier by building the table for us.
function Set:new(items)
local set = {} -- make an efficient search table like before
for key, item in pairs(items) do
set[item] = true
end
-- assign our new search table to use the metamethods from the Set metatable.
setmetatable(set, Set)
return set
end
-- Returns true if our set contains the specified item, otherwise false.
-- The colon method is the same as Set.contain(self, item).So,
-- 'self' is our table of values.
function Set:contain(item)
return self[item] == true
end
-- If we don't overload tostring then it will print the type of the object and it's place in memory. (ex: "table: 0x125").
function Set:__tostring()
return "{" .. table.concat(table.GetKeys(self), ", ") .. "}"
end
-- Overriding the '+' operator.
-- Adding to an existing set will look something like 'set = set + 1'.
-- If you prefer 'set:Add(1)' use the contains method as your template.
function Set:__add(other)
if type(other) == "table" then -- sets get merged
if getmetatable(other) == nil then -- 'other' is a raw table
for key, item in pairs(other) do
self[item] = true
end
else -- 'other' is already a Set (its values are set to true)
for key, item in pairs(other) do
self[key] = true
end
end
else -- single values get appended
self[other] = true
end
return self
end
-- A last bit of useful metamagic
setmetatable(Set, { __call = Set.new }) -- allow Set.new() or Set()
Set.__index = Set -- allow us to use the Set instance methods
```
# A Simple Test
```
local fruits = Set( { "banana", "apple", "strawberry", "orange" } ) + "grape"
local word = "grape"
if fruits:contain( word ) then -- fruits[word] works as well
print( fruits )
end
```
All of the fruits are printed out as expected, though keep in mind that this is an unordered set of elements. If you want them in a specific order,you will have to sort them.
# See also
[Metatables and Metamethods (Programming In Lua)](https://www.lua.org/pil/13.html)