|
|
Line 1: |
Line 1: |
| ==Metatables== | | ===Weak Tables=== |
| {{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?===
| | {{EmphasisBox|Note: Weak Tables require knowledge of [[Scope]] and [[Metatables]]. <br/> |
| | | Also, all Lua objects in the global environment are ignored and will never be garbage collected.|red|dark=yes}} |
| Metatables allow tables to become more powerful than before.
| |
| You may think that if you have code like this:
| |
| | |
| <pre>
| |
| local list = {1,2}
| |
| print(list[3])
| |
| </pre>
| |
| | |
| 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 and see if there's a metatable attached to the list and then return nil if
| |
| there isn't one.
| |
| | |
| ===get and setmetatable===
| |
| There are two main functions when dealing with metatables: getmetatable and
| |
| setmetatable. You use setmetatable to give a table a metatable, and you use
| |
| getmetatable to retrieve the metatable of an object. Here's an example:
| |
| <pre>
| |
| x={}
| |
| metaTable={}
| |
| setmetatable(x, metaTable) --Give x a metatable called metaTable!
| |
| print(getmetatable(x))
| |
| | |
| Output:
| |
| table: [hexadecimal digits]
| |
| </pre>
| |
| | |
| ===Metatable Demonstration===
| |
| | |
| {{Example|1=<i></i> | |
| local List={1,2}<br/>
| |
| print("In table List, index z is "..List.z)<br/>
| |
| Output:<br/><font color="red">attempt to concatenate field 'z' (a nil value)</font color>
| |
| }}
| |
| | |
| z is nil. Now, look at what happens with metatables:
| |
| | |
| {{Example|<pre>
| |
| local List={1,2} -- A normal table
| |
| local Metatable={ -- A metatable
| |
| __index=function(Table, Index)
| |
| rawset(Table, Index, 0) -- Set Table[Index] to 0
| |
| return Table[Index] -- return Table[Index] (which is now 0)
| |
| end
| |
| }
| |
| setmetatable(List, Metatable) -- Now List has the metatable Metatable
| |
| print("In table List, index z is "..List.z)
| |
| | |
| Output:
| |
| In table List, index z is 0
| |
| </pre>}}
| |
| | |
| 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 Table[Index] 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) in an
| |
| index that's nil (List.z), the function associated with __index in Metatable is run. The
| |
| function with __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 equal 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 at 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˚
| |
| *__gc(Object) - Fired when the Object is garbage-collected
| |
| *__len(Object) - Fired when the # operator is used on the Object.
| |
| | |
| ˚ 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 operator to make the table negative:
| |
| | |
| {{Example|<pre>
| |
| local Table1 = {10,11,12}
| |
| | |
| local Metatable = {
| |
| __unm = function (Table) -- __unm is for the unary operator -
| |
| local t={} -- the table to return
| |
| for i,v in pairs(Table) do
| |
| t[i]=-v -- Set t[i] to -1*Table[i]
| |
| end
| |
| return t -- return the negative Table!
| |
| end
| |
| }
| |
| setmetatable(Table1, Metatable)
| |
| print(table.concat(-Table1, "; "))
| |
| | |
| Output:
| |
| -10; -11; -12
| |
| </pre>}} | |
| | |
| Now, you can easily do that with a simple function, but there's a lot more where
| |
| that came from. Take this for example:
| |
| | |
| <pre>
| |
| Table = {10,20,30}
| |
| print(Table(5))
| |
| </pre>
| |
| | |
| Now, obviously you can't call a table. That's just crazy, but (surprise, surprise!)
| |
| with metatables you can.
| |
| | |
| {{Example|<pre>
| |
| local Table = {10,20,30}
| |
| | |
| local Metatable = {
| |
| __call = function (table, param)
| |
| local t = {}
| |
| for i, v in ipairs(table) do
| |
| t[i] = v + param -- Add the argument (5) to the value, then place it in the new table (t).
| |
| end
| |
| return unpack(t) -- Return a string version of t.
| |
| end
| |
| }
| |
| | |
| setmetatable(Table, Metatable)
| |
| print(Table(5))
| |
| | |
| Output:
| |
| 15 25 35
| |
| </pre>}}
| |
| | |
| You can do a lot more as well, such as adding tables!
| |
| | |
| {{Example|<pre>
| |
| local Table1 = {10,11,12}
| |
| local Table2 = {13,14,15}
| |
| | |
| for k, v in pairs(Table1 + Table2) do
| |
| print(k, v)
| |
| end
| |
| </pre>}}
| |
| | |
| This will error saying that you're attempting to perform arithmetic on a table. Let's try this with a metatable.
| |
| | |
| {{Example|<pre>
| |
| local Table1 = {10,11,12}
| |
| local Table2 = {13,14,15}
| |
| | |
| local Metatable = {
| |
| __add = function (table1, table2)
| |
| local t = {}
| |
| for i, v in ipairs(table1) do
| |
| t[i]=v+table2[i] -- t[i] is equal to table1[i]+table2[i]
| |
| end
| |
| return t
| |
| end
| |
| }
| |
| | |
| setmetatable(Table1, Metatable)
| |
| setmetatable(Table2, Metatable)
| |
| | |
| for k, v in pairs(Table1 + Table2) do
| |
| print(k, v)
| |
| end
| |
| | |
| Output:
| |
| 1 23
| |
| 2 25
| |
| 3 27
| |
| </pre>}}
| |
| | |
| | |
| | |
| 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|<pre>
| |
| 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
| |
| </pre>}}
| |
| | |
| ===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|<pre>
| |
| 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
| |
| return num
| |
| end
| |
| | |
| 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.
| |
| </pre>}}
| |
| | |
| ===See Also===
| |
| | |
| http://www.lua.org/manual/5.1/manual.html#2.8
| |
| | |
| http://www.lua.org/pil
| |
| | |
| [[Category:Scripting_Tutorials]]
| |