Difference between revisions of "Lua: Metatables"

From Mario Fan Games Galaxy Wiki
m (was i drunk when i wrote this)
(Cleaning up)
Line 1: Line 1:
 
{{Lua}}
 
{{Lua}}
'''Metatables''' are a way to define behavior in cases where no behavior is defined. Try this code:
+
'''Metatables''' are a form of [[Lua: Tables|table]] that may be attached to any other table, including itself.
 +
 
 +
An individual table may only have one metatable attached, but that metatable may have its own metatable, and this may be cascaded indefinitely. An individual metatable may be attached to any number of tables, however.
 +
 
 +
The primary purpose of metatables is to allow tables to be added together, an operation that is by default impossible.
 +
 
 
  <source lang="lua" enclose="div">table1 = {1, 2, 3}
 
  <source lang="lua" enclose="div">table1 = {1, 2, 3}
 
table2 = {10, 20, 30}
 
table2 = {10, 20, 30}
table3 = table1 + table2 -- error!</source>
+
table3 = table1 + table2 -- Returns an error</source>
you'll just get an error. You can't add [[Lua: Tables|tables]]. Well, that's wrong; you CAN add tables, but you have to define just what 'add' means when tables are involved. Does it mean adding up the values in each row, or appending one table onto the end of another? It can mean either; which one is up to you.
+
 
  
To do this, we must assign a metatable to the table. Metatables are nothing special, they're just regular tables. A table can have only one metatable, but that metatable can have its own metatable, which can have its ''own'' metatable, and so on. However, for performance and complexity reasons, you'll usually just have a single "level". Also, multiple tables can share the same metatable. An interesting note: a table can be its own metatable, which is useful when you start dealing with [[Lua: Methods|methods]].
 
  
To set the metatable for a table, you use the <tt>setmetatable</tt> function, like so:
+
Because tables may contain any number of separate non-nil types of data, adding tables is by default impossible, and will always return an error, due to the overall impracticality of doing so.
<source lang="lua" enclose="div">setmetatable(table, metatable)</source>
 
So going back to our original code, we need to add a new table to use as a metatable:
 
<source lang="lua" enclose="div">mt = { }
 
table1 = {1, 2, 3}
 
table2 = {10, 20, 30}
 
table3 = table1 + table2 -- error! </source>
 
Now we're ready to assign the tables to the metatable. But to make this more flexible, we should write a ''constructor function'' which automatically creates a table and sets its metatable:
 
<source lang="lua" enclose="div">function newTable(t_data)
 
  return setmetatable(t_data, mt)
 
end</source>
 
Then we call it like a regular function, but we pass a table as its parameter, basically like: ''newTable({1,2,3})''. Actually, we can take advantage of another syntax feature to make this more like a regular table construction: if you have a function that takes a single table parameter or single string parameter, you don't need to use the parenthesis for the function call. So we can rewrite our above code's constructors like:
 
<source lang="lua" enclose="div">function newTable(t_data)
 
  return setmetatable(t_data, mt)
 
end
 
  
mt = { }
+
To allow tables to be added together, the [[Lua: Metamethods|metamethod]] ''__add'' must be defined within a metatable, and that metatable must then be appended to a regular table:
table1 = newTable {1, 2, 3}
 
table2 = newTable {10, 20, 30}
 
table3 = table1 + table2 -- error!</source>
 
Well, that didn't seem to help, because we still get an error. Despite what we did, we still didn't define how addition works, so let's do that now. We do this by using a ''metamethod'', a special function used within metatables that are called during certain operations. It turns out that the metamethod for addition is <tt>__add</tt> (those are two underscores). <tt>__add</tt> is a function that has two parameters passed to it{{emdash}}the two added tables. Even if you're adding more than two tables, Lua takes them two at a time. So let's define <tt>__add</tt>:
 
<source lang="lua" enclose="div">mt = {
 
    __add = function (a, b)
 
        local sum = newTable { }
 
        for i = 1, math.max(#a, #b) do -- run as many loops as there are entries for the largest table
 
            sum[i] = (a[i] or 0) + (b[i] or 0) -- the actual addition definition
 
        end
 
        return sum
 
    end
 
}</source>
 
I'm going to point this out right now: notice how we used our <tt>newTable</tt> function with the </tt>sum</tt> table. If you don't do this, that table you just generated can't be added to other tables, because it won't have this metatable associated with it. Don't forget about this when you define other types of metamethods! Also those ''or 0'' parts... those are there to make sure 'holes' in a shorter table won't throw errors. Anyway, our code is ready; this is what it should now look like:
 
  
<source lang="lua" enclose="div">function newTable(t_data)
+
'' ''
   return setmetatable(t_data, mt)
+
== Example code ==
 +
<div style='width: 60%; margin-left: 5%; background: #f8f8f8; border: 1px solid #444; padding: 8px;'><source lang="lua" enclose="div">function newTable(t_data)
 +
   return setmetatable(t_data, mt) -- Assigns the same metatable to every table made with this function
 
end
 
end
  
mt = {  
+
metatable = {  
 
     __add = function (a, b)
 
     __add = function (a, b)
         local sum = newTable { }
+
         local sum = newTable { } -- This is the value we will be returning.
         for i = 1, math.max(#a, #b) do -- run as many loops as there are entries for the largest table
+
         for i = 1, math.max(#a, #b) do -- runs as long as entries exist in the larger table
             sum[i] = (a[i] or 0) + (b[i] or 0) -- the actual addition definition
+
             sum[i] = (a[i] or 0) + (b[i] or 0) -- adds one table entry to its correspondent
 
         end
 
         end
 
         return sum
 
         return sum
Line 60: Line 38:
 
table1 = newTable {1, 2, 3}
 
table1 = newTable {1, 2, 3}
 
table2 = newTable {10, 20, 30}
 
table2 = newTable {10, 20, 30}
table3 = table1 + table2 -- valid!
+
table3 = table1 + table2 -- valid after much headache
 
 
print(table3)</source>
 
Now table3 will contain what you expect; that is, {11, 22, 33}. If you noticed, there is an added <tt>__tostring</tt> metamethod; this is automatically called when an attempt to coerce the table into a string is made, which happens to be the case during <tt>print()</tt>.
 
  
This was just a small example of how metatables and metamethods work. Again, when dealing with methods, you will be using them a lot. For now, try defining the <tt>__sub</tt> and <tt>__mul</tt> metamethods (for subtraction and multiplication, respectively). There are quite a few metamethods, so why not try them out?
+
print(table3) -- Was it worth it?</source></div>

Revision as of 01:29, 6 October 2009

Lua
Lua.gif
Basics
Intermediate
Advanced
XLua
Add to this template
 Standardwikimessagebox.png This article assumes the use of Lua 5.1.

Information may not be accurate or may need revision if you are using a different version.

Metatables are a form of table that may be attached to any other table, including itself.

An individual table may only have one metatable attached, but that metatable may have its own metatable, and this may be cascaded indefinitely. An individual metatable may be attached to any number of tables, however.

The primary purpose of metatables is to allow tables to be added together, an operation that is by default impossible.

table1 = {1, 2, 3}
table2 = {10, 20, 30}
table3 = table1 + table2 -- Returns an error


Because tables may contain any number of separate non-nil types of data, adding tables is by default impossible, and will always return an error, due to the overall impracticality of doing so.

To allow tables to be added together, the metamethod __add must be defined within a metatable, and that metatable must then be appended to a regular table:

Example code

function newTable(t_data)
  return setmetatable(t_data, mt) -- Assigns the same metatable to every table made with this function
end

metatable = { 
    __add = function (a, b)
        local sum = newTable { } -- This is the value we will be returning.
        for i = 1, math.max(#a, #b) do -- runs as long as entries exist in the larger table
            sum[i] = (a[i] or 0) + (b[i] or 0) -- adds one table entry to its correspondent
        end
        return sum
    end,

    __tostring = function (t)
        return table.concat(t, " ")
    end,
}

table1 = newTable {1, 2, 3}
table2 = newTable {10, 20, 30}
table3 = table1 + table2 -- valid after much headache

print(table3) -- Was it worth it?