Skip to content

3. Tables


1. Tables

----------------------------------------------------
-- 3. Tables.
----------------------------------------------------

-- Tables = Lua's only compound data structure;
--          they are associative arrays.
-- Similar to php arrays or js objects, they are
-- hash-lookup dicts that can also be used as lists.

-- Using tables as dictionaries / maps:

-- Dict literals have string keys by default:
T = { key1 = "value1", key2 = false }

-- String keys can use js-like dot notation:
print(T.key1) -- Prints "value1".
print(T.key2) -- Prints "false".
T.newKey = {} -- Adds a new key/value pair.
T.key2 = nil -- Removes key2 from the table.
print(T.key2) -- Prints "nil".

-- Literal notation for any (non-nil) values as key:
U = { ["@!#"] = "gbert", [{}] = 1729, [6.28] = "tau" }
print(U[6.28]) -- Prints "tau"

-- Key matching is basically by value for numbers
-- and strings, but by identity for tables.
A = U["@!#"] -- Now A = "gbert".
B = U[{}] -- We might expect 1729, but it's nil:
-- B = nil since the lookup fails. It fails
-- because the key we used is not the same object
-- as the one used to store the original value. so
-- strings & numbers are more portable keys.

-- A one-table-param function call needs no parens:
function H(x) print(x.key1) end

H { key1 = "sonmi~451" } -- Prints "sonmi~451"

for key, val in pairs(U) do -- Table iteration.
  print(key, val)
end

-- _G is a special table of all globals.
print(_G["_G"] == _G) -- Prints "true".

-- Using tables as lists / arrays:

-- List literals implicitly set up int keys:
V = { "value1", "value2", 1.21, "gigawatts" }
for i = 1, #V do -- #V is the size of V for lists.
  print(V[i]) -- Indices start at 1 !! SO CRAZY!
end
-- A 'list' is not a real type. V is just a table
-- with consecutive integer keys, treated as a list.


1) Metatables and metamethods

----------------------------------------------------
-- 3.1 Metatables and metamethods.
----------------------------------------------------

-- A table can have a metatable that gives the table
-- operator-overloadish behavior. Later we'll see
-- how metatables support js-prototype behavior.

F1 = { a = 1, b = 2 } -- Represents the fraction a/b.
F2 = { a = 2, b = 3 }

-- This would fail:
-- S = F1 + F2

Metafraction = {}
function Metafraction.__add(f1, f2)
  Sum = {}
  Sum.b = f1.b * f2.b
  Sum.a = f1.a * f2.b + f2.a * f1.b

  return Sum
end

setmetatable(F1, Metafraction)
setmetatable(F2, Metafraction)

S = F1 + F2 -- call __add(F1, F2) on F1's metatable

-- F1, F2 have no key for their metatable, unlike
-- prototypes in js, so you must retrieve it as in
-- getmetatable(F1). The metatable is a normal table
-- with keys that Lua knows about, like __add.

-- But the next line fails since a has no metatatble:
-- T = S + S
-- Class-like patterns given below would fix this.

-- An __index on a metatable overloads dot lookups:
DefaultFavs = { animal = "gru", Food = "donuts" }
MyFavs = { food = "pizza" }
setmetatable(MyFavs, { __index = DefaultFavs })
EatenBy = MyFavs.animal -- works! thanks, metatable

-- Direct table lookups that fail will retry using
-- the metatable's __index value, and this recurses.

-- An __index value can also be a function(tbl, key)
-- for more customized lookups.

-- Values of __index, add, .. are called metamethods.
-- Full list. Here a is a table with the metamethod.

-- __add(a, b)                     for a + b
-- __sub(a, b)                     for a - b
-- __mul(a, b)                     for a * b
-- __div(a, b)                     for a / b
-- __mod(a, b)                     for a % b
-- __pow(a, b)                     for a ^ b
-- __unm(a)                        for -a
-- __concat(a, b)                  for a .. b
-- __len(a)                        for #a
-- __eq(a, b)                      for == b
-- __lt(a, b)                      for a < b
-- __le(a, b)                      for a <= b
-- __index(a, b)  <fn or a table>  for a.b
-- __newindex(a, b, c)             for a.b = c
-- __call(a, ...)                  for a(...)


2) Class-like tables and inheritance

----------------------------------------------------
-- 3.2 Class-like tables and inheritance.
----------------------------------------------------

-- Classes aren't built in; there are different ways
-- to make them using tables and metatables.

-- Explanation for this example is below it.

Dog = {} -- 1.

function Dog:new() -- 2.
  NewObj = { sound = "woof" } -- 3.
  self.__index = self -- 4.

  return setmetatable(NewObj, self) -- 5.
end

function Dog:makeSound() -- 6.
  print("I say " .. self.sound)
end

MrDog = Dog:new() -- 7.
MrDog:makeSound() -- "I say woof" -- 8.

-- 1. Dog acts like a class; it's really a table.
-- 2. function tablename:fn(...) is the same as
--    function tablename.fn(self, ...)
--    The : just adds a first arg called self.
--    Read 7 & 8 below for how self gets its value.
-- 3. NewObj will be an instance of class Dog.
-- 4. self = the class being instantiated. Often
--    self = Dog, but inheritance can change it.
--    NewObj gets self's functions when we set both
--    NewObj's metatable and self's __index to self.
-- 5. Reminder: setmetatable returns its first arg.
-- 6. The : works as in 2, but this time we expect
--    self to be an instance instead of a class.
-- 7. Same as Dog.new(Dog), so self = Dog in new().
-- 8. Same as MrDog.makeSound(MrDog); self = MrDog.

-- Below is same with above.
Dog = {}

function Dog.new(table) -- function Dog:new()
  NewObj = { sound = "woof" }
  table.__index = table -- self.__index = self

  return setmetatable(NewObj, table) -- return setmetatable(NewObj, self)
end

function Dog.makeSound(table) -- function Dog:makeSound()
  print("I say " .. table.sound) -- print("I say " .. self.sound)
end

MrDog = Dog.new(Dog) -- MrDog = Dog:new()
MrDog.makeSound(MrDog) -- MrDog:makeSound()

----------------------------------------------------

-- Inheritance example:

LoudDog = Dog:new() -- 1.

function LoudDog:makeSound()
  S = self.sound .. " " -- 2.
  print(S .. S .. S)
end

Seymour = LoudDog:new() -- 3.
Seymour:makeSound() -- "woof woof woof" -- 4.

-- 1. LoudDog gets Dog's methods and variables.
-- 2. self has a 'sound' key from new(), see 3.
-- 3. Same as LoudDog.new(LoudDog), and converted to
--    Dog.new(LoudDog) as LoudDog has no 'new' key,
--    but does have __index = Dog on its metatable.
--    Result: Seymour's metatable is LoudDog, and
--    LoudDog.__index = LoudDog. So Seymour.key will
--    = Seymour.key, LoudDog.key, Dog.key, whichever
--    table is the first with the given key.
-- 4. The 'makeSound' key is found in LoudDog; this
--    is the same as LoudDog.makeSound(Seymour).

-- Below is same with above.
LoudDog = Dog.new(Dog) -- LoudDog = Dog:new()

function LoudDog.makeSound(table) -- function LoudDog:makeSound()
  S = table.sound .. " " -- S = self.sound .. " "
  print(S .. S .. S)
end

Seymour = LoudDog.new(LoudDog) -- Seymour = LoudDog:new()
Seymour.makeSound(Seymour) -- Seymour:makeSound()

-- If needed, a subclass's new() is like the base's:
function LoudDog:new()
  NewObj = {}
  -- set up NewObj
  self.__index = self

  return setmetatable(NewObj, self)
end

References