User:Merlin11188/Draft: Difference between revisions
>Merlin11188 Started editing generic for loop page |
>Merlin11188 Metatables: Didn't really make sense to have raw editing before metamethods... |
||
Line 1: | Line 1: | ||
{{ | {{EmphasisBox|The metatables for [[Strings]] and all ROBLOX types are locked; however, in normal Lua (not RBX.lua) you can set the metatables of these objects using the debug library.[http://www.lua.org/manual/5.1/manual.html#5.9] |green|dark=yes}} | ||
== | ==What is a Metatable?== | ||
Metatables allow tables to become more powerful than before. They are attached to data and contain values called Metamethods. Metamethods are fired when a certain action is used with the datum that it is attached to. | |||
You may think that if you have code like this: | |||
function | |||
<code lua> | |||
local list = {1, 2} | |||
print(list[3]) | |||
</code> | |||
The code will search through the list for the third index in list, realize it's | |||
not there, and return nil. That's totally wrong. What really happens is the code | |||
will search through the list for the third index, realize it's not there, and then | |||
try to see if there's a metatable attached to the table, returning nil if | |||
there isn't one. | |||
==setmetatable() and getmetatable()== | |||
The two primary functions for giving and finding a table's metatable are setmetatable() and getmetatable(). | |||
<code lua> | |||
local x = {} | |||
local metaTable = {} -- metaTables are tables, too! | |||
setmetatable(x, metaTable) -- Give x a metatable called metaTable! | |||
print(getmetatable(x)) --> table: [hexadecimal memory address] | |||
</code> | |||
==Metatable Demonstration== | |||
{{Example|<code lua> | |||
local list = {1, 2} | |||
print("In table List, key \"z\" is "..tostring(list.z)) --> In table List, key "z" is nil | |||
</code> | |||
Now, look at what happens with metatables: | |||
<code lua> | |||
local list = {1, 2} -- A normal table | |||
local metatable = { -- A metatable | |||
__index = function(t, key) | |||
rawset(t, key, 0) -- Set t[key] to 0 | |||
return t[key] -- return t[key] (which is now 0) | |||
end | |||
} | |||
setmetatable(list, metatable) -- Now list has the metatable metatable | |||
print("In table List, key \"z\" is "..tostring(list.z)) --> In table List, key "z" is 0 | |||
</code>}} | |||
What happened there? <tt>list</tt> is a normal table, with nothing unusual about it. <tt>metatable</tt> is also a table, but it has something special: the <tt>__index</tt> metamethod. The <tt>__index</tt> metamethod is fired when <tt>t[key]</tt> is nil, or in this case, <tt>list.z</tt> is nil. Now, nothing would happen because the two tables (<tt>list</tt> and <tt>metatable</tt>) aren't linked. That's what the third line does: sets <tt>list</tt>'s metatable to <tt>metatable</tt>, which means that when <tt>list</tt> is indexed (<tt>__index</tt>) at an index that's nil (<tt>list.z</tt>), the function associated with <tt>__index</tt> in Metatable is run. The function at <tt>__index</tt> in <tt>metatable</tt> uses <tt>rawset</tt> to make a new value in the Table (or <tt>list</tt>). Then, that value is returned. So, <tt>list.z</tt> is set to 0, and then <tt>list.z</tt> is returned (which is 0). | |||
==Metamethods== | |||
Metamethods are the functions that are stored inside a metatable. They can go from | |||
calling a table, to adding a table, to even dividing tables as well. Here's a list | |||
of metamethods that can be used: | |||
*__index(Table, Index) — Fires when Table[Index] is nil. | |||
*__newindex(Table, Index, Value) — Fires when Table[Index] = Value when Table[Index] is nil. | |||
*__call(Table, arg) — Allows the table to be used as a function, arg is the arguments passed, Table(arg). | |||
*__concat(Table, Object) — The .. concatenation operator. | |||
*__unm(Table) — The unary – operator. | |||
*__add(Table, Object) — The + addition operator. | |||
*__sub(Table, Object) — The – subtraction operator. | |||
*__mul(Table, Object) — The * mulitplication operator. | |||
*__div(Table, Object) — The / division operator. | |||
*__mod(Table, Object) — The % modulus operator. | |||
*__pow(Table, Object) — The ^ exponentiation operator. | |||
*__tostring(Table) — Fired when tostring is called on the table. | |||
*__metatable — if present, locks the metatable so getmetatable will return this instead of the metatable and setmetatable will error. Non-function value. | |||
*__eq(Table, Table2) — The == equal to operator˚ | |||
*__lt(Table, Table2) — The < less than operator˚ | |||
*__le(Table, Table2) — The <= operator˚ | |||
*__mode — Used in [[Weak Tables]], declaring whether the keys and/or values of a table are weak. | |||
*__gc(Object) — Fired when the Object is garbage-collected. | |||
*__len(Object) — Fired when the # operator is used on the Object. '''NOTE:''' Only userdatas actually respect the __len() metamethod, this is a bug in Lua 5.1 | |||
˚ Requires two tables; does not work with a table and a number, string, etc. The tables must have the '''same''' metatable. | |||
==Using Metatables== | |||
There are many ways to use metatables, for example the __unm metamethod (to make a table negative): | |||
{{Example|<code lua> | |||
local table1 = {10,11,12} | |||
local metatable = { | |||
__unm = function(t) -- __unm is for the unary operator - | |||
local negatedTable = {} -- the table to return | |||
for key, value in pairs(t) do | |||
negatedTable[key] = -value | |||
end | end | ||
return negatedTable -- return the negative Table! | |||
end | end | ||
} | |||
setmetatable(table1, metatable) | |||
print(table.concat(-table1, "; ")) --> -10; -11; -12 | |||
</code>}} | |||
Here's an interesting way to declare things using __index: | |||
{{Example|<code lua> | |||
local t = {} | |||
local metatable = { | |||
__index = {x = 1} | |||
} | |||
setmetatable(t, metatable) | |||
print(t.x) --> 1 | |||
</code>}} | |||
__index was fired when x was accessed from the table. __index then defined x as 1 instead of nil; therefore, 1 was returned. | |||
Now you can easily do that with a simple function, but there's a lot more where | |||
that came from. Take this for example: | |||
<code lua> | |||
local table = {10, 20, 30} | |||
print(table(5)) | |||
</code> | |||
Now, obviously you can't call a table. That's just crazy, but (surprise, surprise!) | |||
with metatables you can. | |||
{{Example|<code lua> | |||
{{Example|< | local Table = {10, 20, 30} | ||
local metatable = { | |||
__call = function(Table, param) | |||
local sum = {} | |||
for i, value in ipairs(Table) do | |||
sum[i] = value + param -- Add the argument (5) to the value, then place it in the new table (t). | |||
end | end | ||
return unpack(sum) -- Return the individual table values | |||
end | end | ||
} | |||
setmetatable(Table, metatable) | |||
print(Table(5)) --> 15 25 35 | |||
</code>}} | |||
You can do a lot more as well, such as adding tables! | |||
{{Example|<code lua> | |||
local table1 = {10, 11, 12} | |||
local table2 = {13, 14, 15} | |||
for k, v in pairs(table1 + table2) do | |||
print(k, v) | |||
end | end | ||
</code> | |||
This will error saying that you're attempting to perform arithmetic on a table. Let's try this with a metatable. | |||
<code lua> | |||
local table1 = {10, 11, 12} | |||
local table2 = {13, 14, 15} | |||
local metatable = { | |||
__add = function(table1, table2) | |||
local sum = {} | |||
for key, value in pairs(table1) do | |||
if table2[key] ~= nil then -- Does this key exist in that table? | |||
sum[key] = value + table2[key] | |||
else -- If not, add 0. | |||
sum[key] = value | |||
end | |||
end | |||
-- Add all the keys in table2 that aren't in table 1 | |||
for key, value in pairs(table2) do | |||
if sum[key] == nil then | |||
sum[key] = value | |||
end | |||
end | |||
return sum | |||
end | |||
} | |||
setmetatable(table1, metatable) | |||
setmetatable(table2, metatable) | |||
for k, v in pairs(table1 + table2) do | |||
for | print(k, v) | ||
print( | |||
end | end | ||
--[[ | |||
Output: | Output: | ||
That | 1 23 | ||
2 25 | |||
3 27 | |||
]] | |||
</code>}} | |||
If the two tables have two different __add functions, then Lua will go to table1 | |||
first and if it doesn't have an __add function, then it'll go to the second one. That | |||
means that you really only have to set the metatable of Table1 or Table2, but it's | |||
nicer and more readable to set the metatable of both. | |||
Here is one last example breaking away from using separate variables when it isn't necessary. | |||
{{Example|<code lua> | |||
local t = setmetatable({ | |||
10, 20, 30 | |||
}, { | |||
__call = function(a, b) | |||
return table.concat(a, b .. ' ') .. b | |||
end | |||
}) | |||
print('Tables contains '..t(1)) --> Tables contains 101 201 301 | |||
</code>}} | |||
==rawset(), rawget(), rawequal()== | |||
setmetatable() is good for creating a proper metatable, but you might encounter problems. | |||
Let's say that we want to make a table error() whenever you try to access a nil value in it. | |||
<code lua> | |||
local planets = {Earth = "COOL!"} | |||
local mt = { | |||
__index = function(self, i) | |||
return self[i] or error(i .. " is not a real Planet!") | |||
end | |||
} | |||
setmetatable(planets, mt); | |||
print(planets.Earth); --> COOL! | |||
print(planets.Pluto); --> error: C stack overflow | |||
</code> | |||
So what does <tt>C stack overflow</tt> mean? | |||
It means you've called the same function too many times inside of itself (or recursed too deeply). You called a function too many times too quickly! The normal limit is actually right around 20,000! | |||
So what caused the <tt>__index()</tt> method to call itself ? | |||
The answer: <tt>self[i]</tt> | |||
So what can we do? How can we get a member of a table without calling it's <tt>__index()</tt> method? | |||
With [[Function_Dump/Core_Functions#rawget_.28table.2C_index.29|rawget()]], of course! | |||
{{ | {{EmphasisBox|[[Function_Dump/Core_Functions#rawget_.28table.2C_index.29|rawget()]], [[Function_Dump/Core_Functions#rawset_.28table.2C_index.2C_value.29|rawset()]], and [[Function_Dump/Core_Functions#rawequal_.28v1.2C_v2.29|rawequal()]] are very important when making custom metatables because they do not invoke a table's metamethods, so things like __index and __newindex aren't activated!}} | ||
Let's make a small change to the code and try again! | |||
<code lua> | |||
local planets = {Earth = "COOL!"} | |||
local mt = { | |||
__index = function(self, i) | |||
return rawget(self, i) or error(i .. " is not a real Planet!") | |||
end | |||
} | |||
setmetatable(planets, mt) | |||
print(planets.Earth) --> COOL! | |||
print(planets.Pluto) --> error: Pluto is not a real Planet! | |||
</code> | |||
< | |||
==Use Cases== | |||
Now, I am well aware that you can do all of these as a simple function yourself, | |||
but there's a lot more than what you think it can do. Let's try a simple program | |||
that will memorize a number when a possibly laggy math problem is put into it. | |||
For this one we will be using the __index metamethod just to make it simple: | |||
{{Example|<code lua> | |||
local Table = {} | |||
local function mathProblem(num) | |||
for i = 1, 20 do | |||
num = math.floor(num * 10 + 65) | |||
end | |||
for i = 1, 10 do | |||
num = num + i - 1 | |||
end | end | ||
return num | |||
end | end | ||
local | local Metatable = { | ||
__index = function (object, key) | |||
local num = mathProblem(key) | |||
object[key] = num | |||
return num | |||
end | |||
} | |||
local setmetatable(Table, Metatable) | |||
print(Table[1]) -- Will be slow because it's the first time using this number. | |||
print(Table[2]) -- will be slow because it's the first time using this number. | |||
print(Table[1]) -- will be fast because it's just grabbing the number from the table. | |||
</code>}} | |||
</ | |||
== | ==See Also== | ||
http://www.lua.org/manual/5.1/manual.html#2.8 | |||
http://www.lua.org/pil | |||
[[Category: | [[Category:Scripting_Tutorials]] |
Revision as of 23:39, 11 January 2012
What is a Metatable?
Metatables allow tables to become more powerful than before. They are attached to data and contain values called Metamethods. Metamethods are fired when a certain action is used with the datum that it is attached to. You may think that if you have code like this:
local list = {1, 2}
print(list[3])
The code will search through the list for the third index in list, realize it's not there, and return nil. That's totally wrong. What really happens is the code will search through the list for the third index, realize it's not there, and then try to see if there's a metatable attached to the table, returning nil if there isn't one.
setmetatable() and getmetatable()
The two primary functions for giving and finding a table's metatable are setmetatable() and getmetatable().
local x = {}
local metaTable = {} -- metaTables are tables, too!
setmetatable(x, metaTable) -- Give x a metatable called metaTable!
print(getmetatable(x)) --> table: [hexadecimal memory address]
Metatable Demonstration
What happened there? list is a normal table, with nothing unusual about it. metatable is also a table, but it has something special: the __index metamethod. The __index metamethod is fired when t[key] is nil, or in this case, list.z is nil. Now, nothing would happen because the two tables (list and metatable) aren't linked. That's what the third line does: sets list's metatable to metatable, which means that when list is indexed (__index) at an index that's nil (list.z), the function associated with __index in Metatable is run. The function at __index in metatable uses rawset to make a new value in the Table (or list). Then, that value is returned. So, list.z is set to 0, and then list.z is returned (which is 0).
Metamethods
Metamethods are the functions that are stored inside a metatable. They can go from calling a table, to adding a table, to even dividing tables as well. Here's a list of metamethods that can be used:
- __index(Table, Index) — Fires when Table[Index] is nil.
- __newindex(Table, Index, Value) — Fires when Table[Index] = Value when Table[Index] is nil.
- __call(Table, arg) — Allows the table to be used as a function, arg is the arguments passed, Table(arg).
- __concat(Table, Object) — The .. concatenation operator.
- __unm(Table) — The unary – operator.
- __add(Table, Object) — The + addition operator.
- __sub(Table, Object) — The – subtraction operator.
- __mul(Table, Object) — The * mulitplication operator.
- __div(Table, Object) — The / division operator.
- __mod(Table, Object) — The % modulus operator.
- __pow(Table, Object) — The ^ exponentiation operator.
- __tostring(Table) — Fired when tostring is called on the table.
- __metatable — if present, locks the metatable so getmetatable will return this instead of the metatable and setmetatable will error. Non-function value.
- __eq(Table, Table2) — The == equal to operator˚
- __lt(Table, Table2) — The < less than operator˚
- __le(Table, Table2) — The <= operator˚
- __mode — Used in Weak Tables, declaring whether the keys and/or values of a table are weak.
- __gc(Object) — Fired when the Object is garbage-collected.
- __len(Object) — Fired when the # operator is used on the Object. NOTE: Only userdatas actually respect the __len() metamethod, this is a bug in Lua 5.1
˚ Requires two tables; does not work with a table and a number, string, etc. The tables must have the same metatable.
Using Metatables
There are many ways to use metatables, for example the __unm metamethod (to make a table negative):
Here's an interesting way to declare things using __index:
__index was fired when x was accessed from the table. __index then defined x as 1 instead of nil; therefore, 1 was returned.
Now you can easily do that with a simple function, but there's a lot more where
that came from. Take this for example:
local table = {10, 20, 30}
print(table(5))
Now, obviously you can't call a table. That's just crazy, but (surprise, surprise!) with metatables you can.
You can do a lot more as well, such as adding tables!
If the two tables have two different __add functions, then Lua will go to table1
first and if it doesn't have an __add function, then it'll go to the second one. That
means that you really only have to set the metatable of Table1 or Table2, but it's
nicer and more readable to set the metatable of both.
Here is one last example breaking away from using separate variables when it isn't necessary.
rawset(), rawget(), rawequal()
setmetatable() is good for creating a proper metatable, but you might encounter problems.
Let's say that we want to make a table error() whenever you try to access a nil value in it.
local planets = {Earth = "COOL!"}
local mt = {
__index = function(self, i)
return self[i] or error(i .. " is not a real Planet!")
end
}
setmetatable(planets, mt);
print(planets.Earth); --> COOL!
print(planets.Pluto); --> error: C stack overflow
So what does C stack overflow mean? It means you've called the same function too many times inside of itself (or recursed too deeply). You called a function too many times too quickly! The normal limit is actually right around 20,000!
So what caused the __index() method to call itself ? The answer: self[i]
So what can we do? How can we get a member of a table without calling it's __index() method? With rawget(), of course!
Let's make a small change to the code and try again!
local planets = {Earth = "COOL!"}
local mt = {
__index = function(self, i)
return rawget(self, i) or error(i .. " is not a real Planet!")
end
}
setmetatable(planets, mt)
print(planets.Earth) --> COOL!
print(planets.Pluto) --> error: Pluto is not a real Planet!
Use Cases
Now, I am well aware that you can do all of these as a simple function yourself, but there's a lot more than what you think it can do. Let's try a simple program that will memorize a number when a possibly laggy math problem is put into it.
For this one we will be using the __index metamethod just to make it simple: