Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
-- eoo.lua: Edrx'x simple OO scheme, with explanations. -- This file: -- http://angg.twu.net/dednat6/dednat6/eoo.lua.html -- http://angg.twu.net/dednat6/dednat6/eoo.lua -- (find-angg "dednat6/dednat6/eoo.lua") -- Author: Eduardo Ochs <eduardoochs@gmail.com> -- Version: 2019feb22 -- License: GPL3 -- -- A very simple object system. -- The metatable of each object points to its class; -- classes are callable, and act as creators. -- New classes can be created with, e.g.: -- Circle = Class { type = "Circle", __index = {...} } -- then: -- Circle {size = 1} -- sets the metatable of the table {size = 1} to Circle, -- and returns the table {size = 1} (with its __mt modified). -- See the box diagrams below. -- -- Originally from: (find-angg "LUA/canvas2.lua" "Class") -- A tool: (find-angg ".emacs.templates" "class") -- «.Class» (to "Class") -- «.otype» (to "otype") -- «.__mt» (to "__mt") -- «.__mt-box» (to "__mt-box") -- «.lambda» (to "lambda") -- «.Class-short» (to "Class-short") -- «.Vector» (to "Vector") -- «.Vector-box» (to "Vector-box") -- «.Vector-reductions» (to "Vector-reductions") -- «.Class-boxes» (to "Class-boxes") -- «Class» (to ".Class") -- The code for "Class" is just these five lines. Class = { type = "Class", __call = function (class, o) return setmetatable(o, class) end, } setmetatable(Class, Class) -- «otype» (to ".otype") -- This is useful sometimes. -- "otype(o)" works like "type(o)", except on my "objects". otype = function (o) local mt = getmetatable(o) return mt and mt.type or type(o) end -- «over» (to ".over") -- Code for inheritance. -- Note: I have used this only a handful of times! -- See: (find-dn6 "diagstacks.lua" "MetaStack") -- (find-dn6 "diagstacks.lua" "MetaStack" "MetaStack = ClassOver(Stack) {") over = function (uppertable) return function (lowertable) setmetatable(uppertable, {__index=lowertable}) return uppertable end end ClassOver = function (upperclassmt) return function (lowerclass) setmetatable(upperclassmt.__index, {__index=lowerclass.__index}) return Class(upperclassmt) end end -- The first section was adapted from: -- http://angg.twu.net/__mt.html -- (find-TH "__mt") --[[ «__mt» (to ".__mt") 1. The __mt notation ==================== Metatables are explained in detail in section 2.8 of the Lua manual, (find-lua51manual "#2.8" "Metatables") (find-lua51manual "#pdf-setmetatable") 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, "-~->", for _rewritings_. The three rewritings below are standard: T.__foo -~-> T["foo"] foo:bar(...) -~-> foo.bar(foo, ...) function foo (...) ... end -~-> foo = function (...) ... end They are described in these sections of the manual: (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 that the listing above is a translation to the __mt notation of the _cases involving metatables_ that are mentioned in these sections of the manual: (find-lua51manual "#2.8" "op1 + op2") (find-lua51manual "#pdf-tostring") and we follow the order in which these cases are listed in the manual. «__mt-box» (to ".__mt-box") 2. The __mt notation in box diagrams ==================================== We can depict the values of a and b after these assignments a = {10, 20, 30} b = {11, a, "foo", print} as: /---+---------\ /---+----\ b = | 1 : 11 | a = | 1 : 10 | | 2 : * ----------> | 2 : 20 | | 3 : "foo" | | 3 : 30 | | 4 : <print> | \---+----/ \---+---------/ and if after that we do this, using the __mt notation above, T = {__index = b} a.__mt = T we will have: /---+---------\ /---+----\ b = | 1 : 11 | a = | 1 : 10 | | 2 : * ----------> | 2 : 20 | | 3 : "foo" | | 3 : 30 | | 4 : <print> | \mt-+----/ \---+---------/ | ^ v | /-----------+----\ | T = | "__index" : * -----\ | \-----------+----/ | | | \--------------------------------------/ The pointer to the metatable is represented as a "mt" at the floor instead of as a (key, value) pair/line inside the box, Compare: /---+----\ /--------+----\ a = | 1 : 10 | a = | 1 : 10 | | 2 : 20 | instead of: | 2 : 20 | | 3 : 30 | | 3 : 30 | \mt-+----/ | "__mt" : * -----> T | \--------+----/ \----> T Using these diagrams it is easy to calculate a[4] by a series of reductions. Without metatables we would have a[4] = nil, so: a[4] -~-> a.__mt.__index[4] -~-> T.__index[4] -~-> b[4] -~-> print «lambda» (to ".lambda") 3. A lambda notation for Lua functions ====================================== Gavin Wraith implemented two shorthands in his RiscLua: "\" is a shorthand for "function" and "=>" is a shorthand for "return". With them we can write, for example, square = \(x) => x*x end instead of: square = function (x) return x*x end See: http://www.wra1th.plus.com/lua/notes/Scope.html (find-es "lua5" "risclua") We will also use the notation [var := value] for substituion of a single variable, and [var1 := value1, var2 := value2 ...] for simultaneous substitution. A beta-reduction can be calculated in two steps: (\(x) => x*x end)(5) -~-> (x*x)[x:=5] -^-> 5*5 «Class-short» (to ".Class-short") 4. "Class" with shorthands ========================== Remember that "Class" and "otype" were defined as: Class = { type = "Class", __call = function (class, o) return setmetatable(o, class) end, } setmetatable(Class, Class) otype = function (o) -- works like type, except on my "objects" local mt = getmetatable(o) return mt and mt.type or type(o) end With the shorthands for __mt and lambda we can rewrite these as: Class = { type = "Class", __call = \(class, o) o.__mt = class; => o end, } Class.__mt = Class otype = \(o) local mt = o.__mt; => (mt and mt.type or type(o)) end «Vector» (to ".Vector") 5. A class "Vector" =================== Consider this demo code: --[=[ • (eepitch-lua51) • (eepitch-kill) • (eepitch-lua51) dofile "eoo.lua" -- this file 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} -- v = { 3, 4, __mt = Vector} w = Vector {20, 30} -- w = {20, 30, __mt = Vector} 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 --]=] After running the line "w = ..." we will have this: 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, } v = { 3, 4, __mt = Vector, } w = { 20, 30, __mt = Vector, } «Vector-box» (to ".Vector-box") 6. The "Vector" demo in box diagrams ==================================== ...or, in box diagrams: <fcal> = function (class, o) return setmetatable(o, class) end <fadd> = function (V, W) return Vector {V[1]+W[1], V[2]+W[2]} end <ftos> = function (V) return "("..V[1]..","..V[2]..")" end, <fnor> = function (V) return math.sqrt(V[1]^2 + V[2]^2) end /---+---\ /---+----\ v = | 1 : 3 | w = | 1 : 20 | | 2 : 4 | | 2 : 30 | \mt-+---/ \mt-+----/ : ............/ : / vv /--------------+----------\ Vector = | "type" : "Vector" | | "__add" : <fadd> | | "__tostring" : <ftos> | /--------+--------\ | "__index" : * .......> | "norm" : <fnor> | \mt------------+----------/ \--------+--------/ : v /----------+---------\ Class = | "type" : "Class" | | "__call" : <fcal> | \mt--------+---------/ : ^ \..../ «Vector-reductions» (to ".Vector-reductions") 7. The "Vector" demo: reductions ================================ We can use reductions to understand "Vector {3, 4}" and "v:norm()". Being slightly informal at some points, we have: Vector {3, 4} --~-> Vector({3, 4}) --~-> Vector.__mt.__call(Vector, {3, 4}) --~-> Class.__call(Vector, {3, 4}) --~-> <fcal>(Vector, {3, 4}) --~-> (\(class, o) o.__mt=class; => o) (Vector, {3, 4}) --~-> (o.__mt=class; => o) [o:={3, 4}), class:=Vector] --~-> {3, 4, __mt=Vector} v = Vector {3, 4} --~-> v = {3, 4, __mt=Vector} and: 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(V[1]^2+V[2]^2)) [V:={3, 4, __mt=Vector}] --~-> math.sqrt( 3^2+ 4^2) --~-> math.sqrt( 25) --~-> 5 «Class-boxes» (to ".Class-boxes") 6. Class boxes ============== Here's another convention that becomes practical when we get used to the notations above: the "Class box". A Class box for "Vector" depicts Vector on top of Vector.__index, with a horizontal line separating them, and the "__index" and "__class" entries of Vector become implicit in the Class box. We get: /---+---\ /---+----\ v = | 1 : 3 | w = | 1 : 20 | | 2 : 4 | | 2 : 30 | \mt-+---/ \mt-+----/ : ............/ : / vv /-Class--------+----------\ Vector = | "type" : "Vector" | | "__add" : <fadd> | | "__tostring" : <ftos> | +--------------+----------| | "norm" : <fnor> | \--------------+----------/ --]] -- Local Variables: -- coding: utf-8-unix -- End: