User:Merlin11188/Draft: Difference between revisions

From Legacy Roblox Wiki
Jump to navigationJump to search
>Merlin11188
Started editing generic for loop page
>Merlin11188
Metatables: Didn't really make sense to have raw editing before metamethods...
Line 1: Line 1:
{{CatUp|Loops}}
{{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}}
==Introduction==
==What is a Metatable?==


Basic for loops have already been covered in [[Loops]]. If you haven't already done so, please review [[Loops]]. Now, the generic for loop is a loop that traverses through the values returned by an [[iterator]] function. In layman's terms, an iterator function is a function that returns a function that returns some values. WHAAT? Let me show you an example:
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.
{{Example|<pre>
You may think that if you have code like this:
function callFunc(number) -- number is the number we start at
 
-- This function is going to return a function
<code lua>
return function()
local list = {1, 2}
if number < 4 then -- If the number is less than 4
print(list[3])
number = number + 1 -- Add one to it
</code>
return string.char(number + 95)
 
-- And return it's ascii representation plus 95. Note that there's only one value being returned.
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
end
}
 
setmetatable(table1, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
</code>}}


for x in callFunc(1) do -- Since there's only one value being returned, you only need one variable!
Here's an interesting way to declare things using __index:
print(x)
{{Example|<code lua>
end
local t = {}
for y in callFunc(3) do -- Same here.
local metatable = {
print(y)
__index = {x = 1}
end
}
setmetatable(t, metatable)
print(t.x) --> 1
</code>}}


Output:
__index was fired when x was accessed from the table. __index then defined x as 1 instead of nil; therefore, 1 was returned.
a
b
c
c -- string.char(3 + 1 + 95) == string.char(99) == c
</pre>}}
==Discussion==


The iterator is the number that keeps track of how many times a <b>for</b> loop is supposed to run.  This is '''different''' from a the numeric '''for''' loop in [[Loops]] in that the numeric for loop there is simply an iterator:
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>


<b>Numeric for:</b>
Now, obviously you can't call a table. That's just crazy, but (surprise, surprise!)
<pre>
with metatables you can.
for i=20, 1, -2 do print(i) end
</pre>


However, in the generic for loop we get the values returned by the iterator function. In the above function, callFunc, we saw that <code>string.char(number + 95)</code>, not number itself, was returned. Here's an example using multiple return values:
{{Example|<code lua>
{{Example|<pre>
local Table = {10, 20, 30}
function times2(array)
local i = 1 -- This is the iterator value
local metatable = {
return function()
__call = function(Table, param)
if array[i] ~= nil then -- Make sure it's there!
local sum = {}
i = i + 1 -- Add one to i, so this doesn't result in an infinite loop
for i, value in ipairs(Table) do
return i - 1, array[i - 1]*2 -- return the index (i -1) and the value*2 (array[i - 1]*2)
sum[i] = value + param -- Add the argument (5) to the value, then place it in the new table (t).
else
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)


y = {3, 4, 5}
for k, v in pairs(table1 + table2) do
for index, valTimes2 in times2(y) do -- Two, count 'em, two variables. That's because 2 values are returned!
print(k, v)
print(index, valTimes2) -- Print the index and the value times 2 to output.
end
end


--[[
Output:
Output:
1 6
2 8
3 10
</pre>}}


That pretty much covers generic for loops! Read on for some final examples!
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!  


==Examples==


{{Example|<pre>
{{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!}}
months = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}


revmonths = {}
   
for i, v in ipairs(months) do
revmonths[v] = i -- Make the month name the index and the location the value
print(v, revmonths[v])
end


Output:
Let's make a small change to the code and try again!
January 1 -- revmonths.January = 1
<code lua>
February 2 -- revmonths.February = 2
local planets = {Earth = "COOL!"}
March 3
local mt = {
April 4
__index = function(self, i)
May 5 -- Cinco de mayo!
return rawget(self, i) or error(i .. " is not a real Planet!")
June 6
end
July 7
}
August 8
September 9
setmetatable(planets, mt)
October 10
print(planets.Earth)    --> COOL!
November 11
print(planets.Pluto)    --> error: Pluto is not a real Planet!
December 12
</code>
</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|<code lua>
local Table = {}


Here's another example returning multiple values:
local function mathProblem(num)
{{Example|<pre>
for i = 1, 20 do
string = {}
num = math.floor(num * 10 + 65)
string.gfind = function(stringToSearch, Pattern, Start)
end
local start = Start or 1 -- Default value is 1
for i = 1, 10 do
return function()
num = num + i - 1
local beginning, ending = stringToSearch:find(Pattern, start) -- Start searching at the specified location
if beginning and ending then -- Check to make sure that the match is there
start = ending + 1 -- Add one to the ending so the pattern will start to look after the last match
return beginning, ending, stringToSearch:sub(beginning, ending) -- return the 3 values
end
end
end
return num
end
end


local stringToSearch = "Hello! My name is merlin1188!"
local Metatable = {
__index = function (object, key)
local num = mathProblem(key)
object[key] = num
return num
end
}


for start, finish, value in string.gfind(stringToSearch, "%a+") do
local setmetatable(Table, Metatable)
print("The match starts at " .. start ..", finishes at " .. finish .. ", and is " .. value)
end


Output:
print(Table[1]) -- Will be slow because it's the first time using this number.
The match starts at 1, finishes at 5, and is Hello
print(Table[2]) -- will be slow because it's the first time using this number.
The match starts at 8, finishes at 9, and is My
print(Table[1]) -- will be fast because it's just grabbing the number from the table.
The match starts at 11, finishes at 14, and is name
</code>}}
The match starts at 16, finishes at 17, and is is
The match starts at 19, finishes at 24, and is merlin
</pre>}}


==pairs and ipairs==
==See Also==


pairs and ipairs are iterator functions that return table indices and their corresponding values. pairs iterates through the entire table, even if the index is non-numerical (such as "Hi"). ipairs iterates through the table at numerical index, starting at 1 until it reaches nil. Also, please note that pairs does not iterate through the table in any particular order. Here's an example of the difference:
http://www.lua.org/manual/5.1/manual.html#2.8
{{Example|<pre>
sampleTable = {[1] = "A", [2] = 2, [3] = "B", [5] = 5, Hi = "C"}
for i, v in ipairs(sampleTable) do
print(i, v)
end
print("That was with ipairs. This is with pairs.")
for i, v in pairs(sampleTable) do
print(i, v)
end


Output:
http://www.lua.org/pil
1 A -- 1
2 2 -- 2
3 B -- 3
That was with ipairs. This is with pairs.
2 2 -- 2
3 B -- 3
1 A -- 1
5 5 -- 5
Hi C -- Hi
</pre>}}


[[Category:Scripting Tutorials]]
[[Category:Scripting_Tutorials]]

Revision as of 23:39, 11 January 2012

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.[1]

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

Example
{{{1}}}


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):

Example
{{{1}}}


Here's an interesting way to declare things using __index:

Example
{{{1}}}


__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.

Example
{{{1}}}


You can do a lot more as well, such as adding tables!

Example
{{{1}}}


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
{{{1}}}


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!


rawget(), rawset(), and 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! 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:

Example
{{{1}}}


See Also

http://www.lua.org/manual/5.1/manual.html#2.8

http://www.lua.org/pil