Difference between revisions of "Lua: Metatables"

From Mario Fan Games Galaxy Wiki
m (actually...)
m
Line 2: Line 2:
 
{{Lua}}
 
{{Lua}}
 
'''Metatables''' are a way to define behavior in cases where no behavior is defined. Try this code:
 
'''Metatables''' are a way to define behavior in cases where no behavior is defined. Try this code:
  <nowiki>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!</nowiki>
+
table3 = table1 + table2 -- 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.
 
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.
  
Line 10: Line 10:
  
 
To set the metatable for a table, you use the '''setmetatable''' function, like so:
 
To set the metatable for a table, you use the '''setmetatable''' function, like so:
  <nowiki>setmetatable(table, metatable)</nowiki>
+
  <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:
 
So going back to our original code, we need to add a new table to use as a metatable:
  <nowiki>mt = { }
+
  <source lang="lua" enclose="div">mt = { }
 
table1 = {1, 2, 3}
 
table1 = {1, 2, 3}
 
table2 = {10, 20, 30}
 
table2 = {10, 20, 30}
table3 = table1 + table2 -- error! </nowiki>
+
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. We can do it like so, with [[Lua: Vararg|vararg syntax]]:
 
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. We can do it like so, with [[Lua: Vararg|vararg syntax]]:
  <nowiki>function newTable(...)
+
  <source lang="lua" enclose="div">function newTable(...)
 
   local t = ...
 
   local t = ...
 
   setmetatable(t, mt)
 
   setmetatable(t, mt)
 
   return t
 
   return t
end</nowiki>
+
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 like:
 
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 like:
  <nowiki>function newTable(...)
+
  <source lang="lua" enclose="div">function newTable(...)
 
   local t = ...
 
   local t = ...
 
   setmetatable(t, mt)
 
   setmetatable(t, mt)
Line 32: Line 32:
 
table1 = newTable {1, 2, 3}
 
table1 = newTable {1, 2, 3}
 
table2 = newTable {10, 20, 30}
 
table2 = newTable {10, 20, 30}
table3 = table1 + table2 -- error!</nowiki>
+
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 '''__add''' (those are two underscores). '''__add''' 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 '''__add''':
 
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 '''__add''' (those are two underscores). '''__add''' 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 '''__add''':
  <nowiki>mt = { __add = function (a, b)
+
  <source lang="lua" enclose="div">mt = { __add = function (a, b)
 
     local sum = newTable { }
 
     local sum = newTable { }
 
     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 -- run as many loops as there are entries for the largest table
Line 41: Line 41:
 
     return sum
 
     return sum
 
end
 
end
}</nowiki>
+
}</source>
 
I'm going to point this out right now: notice how we used our '''newTable''' function with the '''sum''' 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 (note the added '''table.concat''' function to make the table printable):
 
I'm going to point this out right now: notice how we used our '''newTable''' function with the '''sum''' 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 (note the added '''table.concat''' function to make the table printable):
  
  <nowiki>function newTable(...)
+
  <source lang="lua" enclose="div">function newTable(...)
 
   local t = ...
 
   local t = ...
 
   setmetatable(t, mt)
 
   setmetatable(t, mt)
Line 63: Line 63:
 
table3 = table1 + table2 -- valid!
 
table3 = table1 + table2 -- valid!
  
print(table.concat(table3, " "))</nowiki>
+
print(table.concat(table3, " "))</source>
 
Now table3 will contain what you expect; that is, {11, 22, 33}.
 
Now table3 will contain what you expect; that is, {11, 22, 33}.
  
 
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 '''__sub''' and '''__mul''' metamethods (for subtraction and multiplication, respectively). There are quite a few metamethods, so why not try them out?
 
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 '''__sub''' and '''__mul''' metamethods (for subtraction and multiplication, respectively). There are quite a few metamethods, so why not try them out?

Revision as of 21:50, 16 July 2009

 Standardwikimessagebox.png This page should be syntax highlighted

Code on this page would be more readable and may wrap correctly if it were surrounded by <source> tags using the enclose="div" attribute.

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 way to define behavior in cases where no behavior is defined. Try this code:

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

you'll just get an error. You can't add 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 methods.

To set the metatable for a table, you use the setmetatable function, like so:

setmetatable(table, metatable)

So going back to our original code, we need to add a new table to use as a metatable:

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

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. We can do it like so, with vararg syntax:

function newTable(...)
  local t = ...
  setmetatable(t, mt)
  return t
end

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

function newTable(...)
  local t = ...
  setmetatable(t, mt)
  return t
end

mt = { }
table1 = newTable {1, 2, 3}
table2 = newTable {10, 20, 30}
table3 = table1 + table2 -- error!

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 __add (those are two underscores). __add is a function that has two parameters passed to it—the two added tables. Even if you're adding more than two tables, Lua takes them two at a time. So let's define __add:

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
}

I'm going to point this out right now: notice how we used our newTable function with the sum 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 (note the added table.concat function to make the table printable):

function newTable(...)
  local t = ...
  setmetatable(t, mt)
  return t
end

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
}

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

print(table.concat(table3, " "))

Now table3 will contain what you expect; that is, {11, 22, 33}.

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 __sub and __mul metamethods (for subtraction and multiplication, respectively). There are quite a few metamethods, so why not try them out?