Quick
index
main
eev
eepitch
maths
angg
blogme
dednat6
littlelangs
PURO
(C2,C3,C4,
 λ,ES,
 GA,MD,
 Caepro,
 textos,
 Chapa 1)

emacs
lua
(la)tex
maxima
 qdraw
git
lean4
agda
forth
squeak
icon
tcl
tikz
fvwm
debian
irc
contact

The __mt patch for Lua

1. Understanding the standard metamethods

Metatables are explained in detail in section 2.8 of the Lua manual, but I found out that a certain abuse of language — writing A.__mt for the metatable of A — simplifies a lot the description of what happens in the cases where the metatables are used.

Let's use the squiggly arrow, "⇝" (or "-~->" in ascii), to indicate rewritings. The three rewritings below are standard:

.                  T.__foo  -~->  T["foo"]
              foo:bar(...)  -~->  foo.bar(foo, ...)
function foo (...) ... end  -~->  foo = function (...) return ... end

They are described here:

(find-lua51manual "#2.2"   "a.name")
(find-lua51manual "#2.3"   "var.Name")
(find-lua51manual "#2.5.8" "v:name(args)")
(find-lua51manual "#2.5.9" "function f () body end")

If A and B are tables having the same non-nil metatable, then, modulo details (error conditions, etc), we have:

A + B        -~->  A.__mt.__add(A, B)
A - B        -~->  A.__mt.__sub(A, B)
A * B        -~->  A.__mt.__mul(A, B)
A / B        -~->  A.__mt.__div(A, B)
A % B        -~->  A.__mt.__mod(A, B)
A ^ B        -~->  A.__mt.__pow(A, B)
- A          -~->  A.__mt.__unm(A, A)
A .. B       -~->  A.__mt.__concat(A, B)
#A           -~->  A.__mt.__len(A, A)
A == B       -~->  A.__mt.__eq(A, B)
A ~= B       -~->  not (A == B)
A < B        -~->  A.__mt.__lt(A, B)
A <= B       -~->  A.__mt.__le(A, B)
                   (or not (B < A) in the absence of __le)
A[k]         -~->  A.__mt.__index(A, k)
A[k] = v     -~->  A.__mt.__newindex(A, k, v)
A(...)       -~->  A.__mt.__call(A, ...)
tostring(A)  -~->  A.__mt.__tostring(A)

Note: the listing above follows the same order as the manual.
See: (find-lua51manual "#2.8" "op1 + op2")
     (find-lua51manual "#pdf-tostring")



2. The __mt patch: idea

So here's the idea. It shouldn't be too hard to create a patch for lparser.c (or maybe to use MetaLua or the token filter thing) that implements the following rewrites,

A.__mt      -~-> getmetatable(A)      (when A.__mt is an Rvalue)
A.__mt = B  -~-> setmetatable(A, B)   (when A.__mt is an Lvalue)

yet does not change the meaning of arbitrary accesses to the "__mt" field of a table, e.g., in A["__mt"] — so, __mt should only becomes special when it plays the role of "Name" in the this rule in Lua's grammar:

var ::=  Name | prefixexp `[' exp `]' | prefixexp `.' Name 

That patch would not affect at all the performance of (byte-)compiled Lua programs, and it would let use notations like

A + B   -~->   A.__mt.__add(A, B)

as more than pseudo-code...



3. The __mt patch: an implementation

In 2010dec27 I discussed this idea with Marc Simpson, and he quickly realized the main uses for it would be in teaching and in doing experiments with metatables — for example, when we are examining or devising an OO scheme —, and so it makes more sense to implement it in the simplest possible way, even if the patched Lua interpreter would run more slowly than a standard one... and he wrote a working patch, which uses a strcmp to intercept all accesses to an "__mt" field in tables. His patch is here and here.



4. eoo.lua

Using the __mt notation and Gavin Wraith's "\" and "=>" shorthands, my favorite OO system, eoo.lua, becomes easy to explain. Its code is just:

-- (find-blogme4 "eoo.lua")
Class = {
    type   = "Class",
    __call = \ (class, o) => o.__mt = class end,
  }
Class.__mt = Class

otype = function (o)
    local mt = o.__mt
    return mt and mt.type or type(o)
  end

and here is a test for it:

* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
-- (find-angg "LUA/lua50init.lua" "ee_dofile")
-- (find-blogme4 "eoo.lua")
ee_dofile "~/blogme4/eoo.lua"

-- (find-angg ".emacs.templates" "class")
Vector = Class {
  type    = "Vector",
  __add   = function (V, W) return Vector { V[1]+W[1], V[2]+W[2] } end,
  __tostring = function (V) return "("..V[1]..","..V[2]..")" end,
  __index = {
    norm = function (V) return math.sqrt(V[1]^2 + V[2]^2) end,
  },
}

v = Vector {3, 4}
w = Vector {20, 30}
print(v)             --> (3,4)
print(v + w)         --> (23,34)
print(v:norm())      --> 5
print( type(v))      --> table
print(otype(v))      --> Vector
print( type(""))     --> string
print(otype(""))     --> string

Here is how it works. We have:

Class = {
  type   = "Class",
  __call = \(class, o) => o.__mt = class end,
  __mt   = Class
}
Vector = {
  type       = "Vector",
  __add      = \(V, W) => {V[1]+W[1], V[2]+W[2]} end,
  __tostring = \(V) => "("..V[1]..","..V[2]..")" end,
  __index    = { norm = \(V) => math.sqrt(V[1]^2+V[2]^2) end },
  __mt       = Class
}

and so:

v   = Vector {3, 4}
--~-> Vector({3, 4})
--~-> Vector.__mt.__call(Vector, {3, 4})
--~->       Class.__call(Vector, {3, 4})
--~-> (\(class, o) =>      o.__mt = class  end)(Vector, {3, 4})
--~->         (\() => {3, 4}.__mt = Vector end)()
--~->                 {3, 4, __mt = Vector}

and then:

v:norm()
--~->  {3, 4, __mt=Vector}:norm()
--~->  {3, 4, __mt=Vector}.norm({3, 4, __mt=Vector})
--~->  {3, 4, __mt=Vector}.__mt.__index.norm({3, 4, __mt=Vector})
--~->                    Vector.__index.norm({3, 4, __mt=Vector})
--~-> (\(V) => math.sqrt(V[1]^2+V[2]^2) end)({3, 4, __mt=Vector})
--~->  (\() => math.sqrt(   3^2+   4^2) end)()
--~->  (\() =>                  5       end)()
--~->                           5



This idea of using reductions is useful in other cases too, that do not involve metatables. Here is an e-mail that I sent to Marc in 2010dec29, about a notation for tracking upvalues. Some of the ideas in it were inspired by Paul Blain Levy's book.