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://anggtwu.net/LUA/eoo.lua.html
--   http://anggtwu.net/LUA/eoo.lua
--          (find-angg "LUA/eoo.lua")
-- Author: Eduardo Ochs <[email protected]>
-- Version: 2021sep12
-- License: GPL3 - if you need another license, ask me.
--
-- A very simple object system.
-- Here's a _very_ short description of how it works:
--
--   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).
--
-- There are real docs (with lots of diagrams!) below.
--
-- Version in my init file: (find-angg "LUA/lua50init.lua" "eoo")
--         Originally from: (find-angg "LUA/canvas2.lua"  "Class")
--                  A tool: (find-angg ".emacs.templates" "class")
--            Compare with: http://lua-users.org/wiki/ObjectOrientedProgramming

-- «.Class»			(to "Class")
-- «.otype»			(to "otype")

-- Docs:
--   «.syntax-sugars»		(to "syntax-sugars")
--   «.__mt»			(to "__mt")
--   «.__mt-box»		(to "__mt-box")
--   «.Vector»			(to "Vector")
--   «.Vector-reductions»	(to "Vector-reductions")
-- Tests:
--   «.test-Vector»		(to "test-Vector")
--   «.test-override»		(to "test-override")



-- «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")
-- "otype(o)" works like "type(o)", except on my "objects" and on
-- objects whose metatables are registered in otype_metatables.
-- For example, after doing
--   otype_metatables[getmetatable(lpeg.P(1))] = "lpeg"
-- then the otype of any lpeg pattern will be "lpeg".
otype_metatables = {}

otype = function (o)
    local  mt = getmetatable(o)
    return mt and (otype_metatables[mt] or mt.type) or type(o)
  end




-- «over» (to ".over")
-- Code for inheritance.
-- Note: I have used this only a handful of times, and ages ago!
-- 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






--  ____                 
-- |  _ \  ___   ___ ___ 
-- | | | |/ _ \ / __/ __|
-- | |_| | (_) | (__\__ \
-- |____/ \___/ \___|___/
--                       
--[==[

 «syntax-sugars»  (to ".syntax-sugars")

1. Syntax sugars
================
The best way to explain _in all details_ how this system of classes
and objects works is by introducing some new notations, or "syntax
sugars", that will be valid only in these comments; they are not
implemented in the Lua parser. The squiggly arrow "⇝" will mean
"desugars to", and is used like this:

                new notation  ⇝  more basic notation

In Lua these three syntax sugars are standard:

                       T.foo  ⇝  T["foo"]
  function foo (...) ... end  ⇝  foo = function (...) ... end
                foo:bar(...)  ⇝  foo.bar(foo, ...)

They are explained in these sections of the manual:

  (find-lua51manual "#2.2" "field name as an index" "a.name"   "a[\"name\"]")
  (find-lua51manual "#2.3"                        "var.Name" "var[\"Name\"]")
  (find-lua51manual "#2.5.8" "v:name(args)" "v.name(v, args)")
  (find-lua51manual "#2.5.9" "function f () body end")
  (find-lua51manual "#2.5.9" "colon syntax")

Our two first syntax sugars are these: "λ" will be an abbreviation for
"function", and "⇒" will be an abbreviation for "return". So:

  λ(a,b) ⇒ expr end   ⇝   function (a,b) return expr end

Our third syntax sugar will be the "__mt" notation for metatables - we
will pretend that the field ".__mt" of a table accesses its metatable.
So:

         B.__mt       ⇝   getmetatable(B)
         B.__mt = A   ⇝   setmetatable(B, A)

Now some diagrammatic notations. We will draw tables as boxes with two
coluns in which each line represents a key-value pair, like this:

         ╭──────────╮ 
  	 │  1  ┆ 10 │ 
  	 │  2  ┆ 20 │  ⇝  {10, 20, a=30, ["!"]=40}
  	 │ "a" ┆ 30 │ 
  	 │ "!" ┆ 40 │ 
  	 ╰──────────╯

This Lua code

  A = {10, 20, 30}
  B = {40, 50, a=A}

builds two tables, that we will draw like this:

        ╭──────────╮         ╭──────────╮ 
    B = │  1  ┆ 40 │     A = │  1  ┆ 10 │ 
        │  2  ┆ 50 │         │  2  ┆ 20 │  
        │ "a" ┆   ───────>  │  3  ┆ 30 │  
        ╰──────────╯         ╰──────────╯  

A table with a metatable will be drawn in special way, with an "mt"
at its bottom left pointing to its metatable. This code

  A = {10, 20, 30}
  C = {40, 50}
  setmetatable(A, C)

generates the structures below; note that the "⇝" in the middle
indicates that the diagram at the left is a syntax sugar for the
diagram at the right...

        ╭──────────╮            ╭─────────────╮ 
    C = │  1  ┆ 40 │        C = │    1   ┆ 40 │ 
        │  2  ┆ 50 │            │    2   ┆ 50 │ 
        ╰mt────────╯            │ "__mt" ┆   │ 
          │            ⇝        ╰───────────│─╯ 
          ╰──╮                        ╭─────╯   
             v                        v           
        ╭──────────╮             ╭──────────╮     
    A = │  1  ┆ 10 │         A = │  1  ┆ 10 │     
        │  2  ┆ 20 │             │  2  ┆ 20 │     
        │  3  ┆ 30 │             │  3  ┆ 30 │     
        ╰──────────╯             ╰──────────╯     

We will use the notation <func> to denote a function - that may or may
not have a global name. For example, this code,

  Class = {
      type   = "Class",
      __call = function (class, o) return setmetatable(o, class) end,
    }
  setmetatable(Class, Class)

creates this in the memory:

   <fcal> = function (class, o) return setmetatable(o, class) end

           ╭────────────────────╮     
   Class = │ "type"   ┆ "Class" │     
           │ "__call" ┆ <fcal>  │     
           ╰mt──────────────────╯     
             │    ^                   
             ╰────╯                    

If we didn't have the trick of giving names like "<bla>" to function
objects we would have to use bullets an arrows, like " ──> ...", to
show that the field "__call" above points to a certain function.

From here onwards "Class" will always denote the table "Class" above.

Our last (diagrammatic) syntax sugar will give us a convenient way to
draw "normal" classes, i.e., classes that are not the class "Class".
Every "normal" class - for example: Vector - is made of two tables,
one for "the class itself" and another one for its "methods". When we
run this code,

  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,
    },
  }

this will create these structures in the memory:

   <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,
   <fnrm> = function (V)        return math.sqrt(V[1]^2 + V[2]^2) end

           ╭─Class───────────────────╮              ╭─────────────────────────╮
  Vector = │ "type"       ┆ "Vector" │     Vector = │ "type"       ┆ "Vector" │        
           │ "__add"      ┆ <fadd>   │              │ "__add"      ┆ <fadd>   │        
           │ "__tostring" ┆ <ftos>   │              │ "__tostring" ┆ <ftos>   │ 
           ├─────────────────────────│   ⇝          │ "__index"    ┆         │        
           │ "norm"       ┆ <fnrm>   │              ╰mt─────────────────│─────╯ 
           ╰─────────────────────────╯                │              ╭──╯      
                                                      │              v         
                                                      │     ╭─────────────────╮        
                                                      │     │ "norm" ┆ <fnrm> │        
                                                      │     ╰─────────────────╯        
                                                      v                                
                                                    ╭────────────────────╮     
                                            Class = │ "type"   ┆ "Class" │     
                                                    │ "__call" ┆ <fcal>  │     
                                                    ╰mt──────────────────╯     
                                                      │    ^                   
                                                      ╰────╯                    

Note the "⇝" indicating that the diagram at the left is a
(diagrammatic) syntax sugar for the diagram at the right... the upper
part of the box, above the horizontal line, is "the class itself", and
the part below the horizontal line is its "table of methods". The
metatable of the "class itself" points to the table Class, that is
omitted from the drawing at the left, and ".__index" field points to
the "table of methods".



 «__mt» (to ".__mt")

2. 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 in a way that I find quite difficult. I found out that the "__mt
notation", described above, simplifies A LOT the description of _most_
of what happens when metatables are used and the metamethods like
"__index" or "__add" are called.

If A and B are tables having the same non-nil metatable, then modulo
details, we have these "reductions":

  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)

The term "reduction" was borrowed from λ-calculus, but I am using it
here in an imprecise way. The listing above is a translation to the
__mt-notation of the "main cases" 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.
The "main cases" are the cases in which both A and B are tables with
the same metatable, the expected method - like "__add" - is a field in
that metatable, and no errors happen. The manual also describes what
happens when we are not in the "main case"; then the expressions at
the left in the table above may "reduce" to other expressions.






 «__mt-box» (to ".__mt-box")

3. The __mt notation and box diagrams
=====================================
If we combine the reductions of the previous section with the box
diagrams from section 1 we can visualize how some quite intrincate
operations happen.

For example, we can depict the values of a and b after these
assignments,

  a = {10, 20, 30}
  b = {11, a, "foo", print}
  T = {__index = b}
  a.__mt = T

as:

      ╭─────────────╮         ╭────────╮
  b = │ 1 ┆    11   │     a = │ 1 ┆ 10 │
      │ 2 ┆     * ──────────> │ 2 ┆ 20 │
      │ 3 ┆  "foo"  │         │ 3 ┆ 30 │
      │ 4 ┆ <print> │         ╰mt──────╯
      ╰─────────────╯           │
           ^                    v
           │                 ╭────────────────╮
           │             T = │ "__index" ┆  ─────╮
           │                 ╰────────────────╯   │
           │                                      │
           ╰──────────────────────────────────────╯

Now let's try 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>

So a[4] yields <print>.




 «Vector» (to ".Vector")

4. 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:

   <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"    ┆         │
            ╰mt────────────────│──────╯    
              │                v
              │        ╭─────────────────╮
              │	       │ "norm" ┆ <fnor> │
              │	       ╰─────────────────╯
              v
            ╭────────────────────╮
    Class = │ "type"   ┆ "Class" │
            │ "__call" ┆ <fcal>  │
            ╰mt──────────────────╯
              │    ^
              ╰────╯



 «Vector-reductions» (to ".Vector-reductions")

5. The "Vector" demo: reductions
================================
We can now use the diagram above and our notation for reductions to
understand how expressions like these ones

  v = Vector {3, 4}
  v:norm()

work. Remember that we can calculate the result of applying a
λ-expression to its arguments in two steps, like this:

  (λ(a,b) ⇒ a+b) (3, 4)  ⇝  (a+b) [a:=3, b:=4]
                         ⇝  (3+4)

The "[a:=3, b:=4]" means "substitute all `a's by 3 and all `b's by 4
in the preceding expression".

Being slightly informal at some steps, we have this:

     Vector {3, 4}
  ⇝  Vector({3, 4})
  ⇝  Vector.__mt.__call(Vector, {3, 4})
  ⇝        Class.__call(Vector, {3, 4})
  ⇝              <fcal>(Vector, {3, 4})
  ⇝  (λ(class, o) ⇒ setmetatable(o, class)) (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}

and so:

    v = Vector {3, 4}
  ⇝ v =        {3, 4, __mt=Vector}

I am using ideas borrowed from basic λ-calculus to talk about mutable
objects, so I had to improvise at some points.

Here's what happens when we calculate "v:norm()":

     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





6. Overriding methods
=====================
(I use this a lot. TODO: write this section!)
Demo:




Everything below this point
will be rewritten and/or deleted.
--snip--snip--

The section on __mt reductions was adapted from:
  http://anggtwu.net/__mt.html
           (find-TH "__mt")

Gavin Wraith implemented two shorthands in his RiscLua: "\" is a
shorthand for "function" and "=>" is a shorthand for "return". We
will use unicode characters instead: "λ" will be a shorthand for
"function" and "⇒" will be a shorthand for "return". With that
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

--]==]





--  _____         _       
-- |_   _|__  ___| |_ ___ 
--   | |/ _ \/ __| __/ __|
--   | |  __/\__ \ |_\__ \
--   |_|\___||___/\__|___/
--                        
--[[
-- «test-Vector»  (to ".test-Vector")

* (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


-- «test-override»  (to ".test-override")
-- Eoo.lua is "REPL-friendly" in the sense that it is easy to add new
-- methods to a class, and to override old methods, from a REPL.

* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "eoo.lua"
Foo = Class {
  type = "Foo",
  new        = function (v) return Foo({v=v}) end,
  __tostring = function (o) return o:tostring() end,
  __index = {
    tostring = function (o) return "(" .. o.v .. ")" end
  },
}

a = Foo.new(42)
= a              --> (42)
b = Foo.new(99)
= b              --> (99)

-- This overrides the default ":tostring" of the class:
Foo.__index.tostring = function (o) return "[" .. o.v .. "]" end
= a              --> [42]
= b              --> [99]

-- This overrides the ":tostring" method in a single instance:
a.tostring           = function (o) return "<" .. o.v .. ">" end
= a              --> <42>
= b              --> [99]

-- This creates an instance with an overriden ":tostring" method:
c = Foo {v=200, tostring = function (o) return "<<" .. o.v .. ">>" end}
= c              --> <<200>>

--]]







-- Local Variables:
-- coding:  utf-8-unix
-- End: