add Lua classes tutorial
This commit is contained in:
		
							parent
							
								
									05e60920a7
								
							
						
					
					
						commit
						7cf5fbf843
					
				
					 3 changed files with 517 additions and 2 deletions
				
			
		
							
								
								
									
										495
									
								
								content/programming/lua/classes.tree
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										495
									
								
								content/programming/lua/classes.tree
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,495 @@
 | 
			
		|||
%% title = "implementing classes in Lua"
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5DHSAN8FNC27WM0RE5"
 | 
			
		||||
- while reading Lua, you may have stumbled upon something that looks like this:
 | 
			
		||||
 | 
			
		||||
```lua
 | 
			
		||||
-- Declare a base `Entity` class...
 | 
			
		||||
 | 
			
		||||
local Entity = Object:inherit()
 | 
			
		||||
 | 
			
		||||
function Entity:tick() end
 | 
			
		||||
function Entity:draw() end
 | 
			
		||||
 | 
			
		||||
-- and an inheriting `Player` class.
 | 
			
		||||
 | 
			
		||||
local Player = Entity:inherit()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DDM42QXJ6RQJ6MYGG"
 | 
			
		||||
    - this is the way people generally do object-oriented programming in the language.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DAM1JSKSHDPPT5DZ3"
 | 
			
		||||
    - for someone coming from a language like Java, where classes are a syntactic construct---`class Cat extends Animal`---it can feel weird to see them declared this way---as local variables, using regular functions to implement inheritance.
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DJEJ6EM5NM24EWZW0"
 | 
			
		||||
        - but worry not!
 | 
			
		||||
        this tutorial will hopefully clear up any confusion you might have, using beginner-friendly language, and simple examples.
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5D3M7RY6D9EXMXEB3P"
 | 
			
		||||
- ### metatables
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DX4PEJ1GW27S257T3"
 | 
			
		||||
    - before we start, we need to talk about *metatables*.
 | 
			
		||||
    these are Lua's way of allowing users to _overload operators_.
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DBRZA5EAF7D6C11KA"
 | 
			
		||||
        - operators include arithmetic: `+`, `-`, `*`, `/`, but also things like indexing tables `a[b]`, creating new indices in tables `a[b] = c`, or function calls `a(b, c, d)`.
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DWNC80HZAXXQ9JR7Z"
 | 
			
		||||
        - we call it operator _overloading_, because we _overload_ the default meaning of the operator with our own, custom definition.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D62EMCSEPTQAN82XH"
 | 
			
		||||
    - we can set the metatable of a table using [`setmetatable(t, metatable)`](https://www.lua.org/manual/5.4/manual.html#pdf-setmetatable).
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5D86T78ESRTQBEJ5T4"
 | 
			
		||||
        - the `metatable` is another table, that contains fields for overriding these operators.
 | 
			
		||||
 | 
			
		||||
            % id = "01JKKQHG5DMCH6P2X8C23EHKW0"
 | 
			
		||||
            - the most important field of metatables we'll be focusing on today is `__index`, which defines a _fallback_ for the `a[b]` operator---and by extension, also `a.b`, which is syntactic sugar for `a["b"]`.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DCNFHQ72NGAE4T12D"
 | 
			
		||||
    - #### `__index`
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DXTZDGY9CHH456388"
 | 
			
		||||
        - the `__index` field is used when an otherwise `nil` field is accessed in a table.
 | 
			
		||||
        consider this:
 | 
			
		||||
 | 
			
		||||
        ```lua
 | 
			
		||||
        local t = { a = 1 }
 | 
			
		||||
        print(t.b) --> nil
 | 
			
		||||
        ```
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DR51DKCN70ZJAGBM1"
 | 
			
		||||
        - in this case, `t` does not have a metatable with `__index`, so `nil` is returned.
 | 
			
		||||
        to change this behaviour, we override `__index` by telling Lua a function to run whenever the key doesn't exist.
 | 
			
		||||
 | 
			
		||||
        ```lua
 | 
			
		||||
        local fallback = { b = 2 }
 | 
			
		||||
 | 
			
		||||
        setmetatable(t, {
 | 
			
		||||
            -- The first argument is the table that's indexed, and the second argument is the index,
 | 
			
		||||
            -- i.e. the arguments map to `the_table[index]`.
 | 
			
		||||
            __index = function (the_table, index)
 | 
			
		||||
                return fallback[index]
 | 
			
		||||
            end,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        print(t.b) --> 2
 | 
			
		||||
        ```
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DG2EGZTBEZ38YK8KW"
 | 
			
		||||
        - however, there is a more compact and faster way of doing this.
 | 
			
		||||
        `__index` is special, because in addition to being able to set it to a function, we can also set it to a table:
 | 
			
		||||
 | 
			
		||||
        ```lua
 | 
			
		||||
        setmetatable(t, {
 | 
			
		||||
            __index = fallback,
 | 
			
		||||
        })
 | 
			
		||||
        print(t.b) --> 2
 | 
			
		||||
        ```
 | 
			
		||||
 | 
			
		||||
        this avoids the need to allocate a local function, which can be costly if you run it many times in a game loop!
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5DTZG3T5NQYWSQYGQF"
 | 
			
		||||
- ### method call syntax
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DYJBYRMSK7MJWZT5F"
 | 
			
		||||
    - there's one thing we need to get out of the way before we move on, and that is Lua's _method call syntax_ `a:method(b)`.
 | 
			
		||||
    this syntax is equivalent to:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    a.method(a, b)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5D35F2P45MA33QNAER"
 | 
			
		||||
        - basically, the thing before `:` is passed as the first argument to the thing before `:`'s `method` function.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DC2MDYZ9M07ZZ8J9D"
 | 
			
		||||
    - Lua also has a syntax sugar for declaring functions on tables:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local t = {}
 | 
			
		||||
 | 
			
		||||
    function t.do_stuff()
 | 
			
		||||
        print("hi")
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DTF67PWWTWPFMYT6Q"
 | 
			
		||||
    - to complement the `:` call syntax, there's also the `:` function declaration syntax.
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    function t:do_thing()
 | 
			
		||||
        self.aaa = 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    -- desugars to
 | 
			
		||||
 | 
			
		||||
    function t.do_thing(self)
 | 
			
		||||
        self.aaa = 1
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    as this example shows, this syntax simply inserts a parameter named `self` before all other parameters.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D2QEHBJ32NF0VCFEX"
 | 
			
		||||
    - the call and declaration syntaxes are not tied together in any way, so the dot and colon syntax could be mixed however one wants, but it's probably better not to.
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DNY2EPFN195KYJVEW"
 | 
			
		||||
        - bear in mind that your function declarations also serve the purpose of documentation, and using the `:` syntax in declarations makes it clearer you're supposed to call the functions with the `:` syntax.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DY4JZDZTB5B7W9D8N"
 | 
			
		||||
    - with that knowledge, we can move on to creating classes.
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5DCTRRFQQ7NNNBM8XS"
 | 
			
		||||
- ### classes
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D4M3K6D4ECF2M2QFD"
 | 
			
		||||
    - we can use `__index` fallback tables to model classes quite easily.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D8QVHGTDGEP9RYBJG"
 | 
			
		||||
    - let's create a class `Cat` with two methods `meow` and `feed`:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local Cat = {}
 | 
			
		||||
 | 
			
		||||
    function Cat:meow()
 | 
			
		||||
        print("meow")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function Cat:feed()
 | 
			
		||||
        self.food = self.food + 1
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DWP99Q74Q62RVS176"
 | 
			
		||||
    - we also need a method for creating cats, which I'll call `new`:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    function Cat:new()
 | 
			
		||||
        local cat = {}
 | 
			
		||||
        cat.food = 10
 | 
			
		||||
        return cat
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DDVYTWXZYS711DVWT"
 | 
			
		||||
    - we can now use the API like this:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local kitty = Cat:new()
 | 
			
		||||
    Cat.meow(kitty)
 | 
			
		||||
    Cat.feed(kitty)
 | 
			
		||||
    print(kitty.food) --> 11
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    but, note how we have to namespace the `Cat` functions specifically, and we cannot use the `:` method call operator yet.
 | 
			
		||||
    the table returned by `Cat:new()` does not have the methods `meow` and `feed` for that to work.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D0KT6YAR9MWAC00N0"
 | 
			
		||||
    - so to provide it with these methods, we can use our handy `__index` metamethod:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    function Cat:new()
 | 
			
		||||
        local cat = {}
 | 
			
		||||
        cat.food = 10
 | 
			
		||||
        -- setmetatable returns its first argument. How convenient!
 | 
			
		||||
        return setmetatable(cat, { __index = Cat })
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DQZRNTCWK2JACFJWT"
 | 
			
		||||
    - _now_ we'll be able to create cats that can meow on their own:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    kitty = Cat:new()
 | 
			
		||||
    kitty:meow()
 | 
			
		||||
    kitty:feed()
 | 
			
		||||
    print(kitty.food) --> 11
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DK3S08JBG24970YV6"
 | 
			
		||||
    - however, creating an extra metatable every single time we create a cat is pretty inefficient!
 | 
			
		||||
    we can exploit the fact that Lua doesn't really care about metatable fields it doesn't know about, and make `Cat` itself into a metatable:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    Cat.__index = Cat
 | 
			
		||||
 | 
			
		||||
    function Cat:new()
 | 
			
		||||
        local cat = {}
 | 
			
		||||
        cat.food = 10
 | 
			
		||||
        return setmetatable(cat, Cat)
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DYG4C3Y5H4WEYQ025"
 | 
			
		||||
    - but note how we've declared `Cat:new` with the special method syntax.
 | 
			
		||||
    we call the method like `Cat:new()`, which desugars to `Cat.new(Cat)`, which means that the implicit `self` parameter _is_ already the `Cat` table!
 | 
			
		||||
    thus, we can simplify the call to `setmetatable`, to remove the redundant reference to `Cat`:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
        return setmetatable(cat, self)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D1ZTAT0JPNPWDE2WC"
 | 
			
		||||
    - with all these improvements, here's how the code looks so far.
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local Cat = {}
 | 
			
		||||
    Cat.__index = Cat
 | 
			
		||||
 | 
			
		||||
    function Cat:new()
 | 
			
		||||
        local cat = {}
 | 
			
		||||
        cat.food = 10
 | 
			
		||||
        return setmetatable(cat, self)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function Cat:meow()
 | 
			
		||||
        print("meow!")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function Cat:feed()
 | 
			
		||||
        self.food = self.food + 1
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5DHXF00NJPPVXBZ98K"
 | 
			
		||||
- ### inheritance
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DB3JWK6YCG1N47JZ1"
 | 
			
		||||
    - given this fairly simple way of creating classes, we can now expand this idea to inheritance.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D7N0A33S109YF2NV6"
 | 
			
		||||
    - conceptually, inheriting from a class is pretty simple: what we want to do, is to have all of the parent class's methods available on the child class.
 | 
			
		||||
    I think you might see where this is going now: all we need to do to create a subclass, is to create a new class, whose metatable's `__index` points to the parent class.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DSB0DQPPWQZNPWY1M"
 | 
			
		||||
    - let's rewrite our example with the kitty to generalise animals under a single class:
 | 
			
		||||
 | 
			
		||||
    ```
 | 
			
		||||
    Animal
 | 
			
		||||
    - food: integer
 | 
			
		||||
    : speak()
 | 
			
		||||
    : feed()
 | 
			
		||||
 | 
			
		||||
    Cat : Animal
 | 
			
		||||
    : speak()
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D4PZMTFRFV4T6HSA1"
 | 
			
		||||
    - so, starting with the base `Animal` class...
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local Animal = {}
 | 
			
		||||
    Animal.__index = Animal
 | 
			
		||||
 | 
			
		||||
    -- We don't create a `new` method, because we don't want people creating "generic" animals.
 | 
			
		||||
    -- This makes our class _abstract_.
 | 
			
		||||
 | 
			
		||||
    -- speak is a function that must be overridden by all subclasses, so we make it error by default.
 | 
			
		||||
    function Animal:speak() error("not implemented") end
 | 
			
		||||
 | 
			
		||||
    function Animal:feed()
 | 
			
		||||
        self.food = self.food + 1
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D0ENDQ5QQTBAA79GQ"
 | 
			
		||||
    - we can define a `Cat` class as a subclass of `Animal`:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local Cat = {}
 | 
			
		||||
    -- We still need to override __index, so that the metatable we set in our own constructor
 | 
			
		||||
    -- has our overridden `speak()` method. 
 | 
			
		||||
    Cat.__index = Cat
 | 
			
		||||
    -- To be able to call `Animal` methods from `Cat`, we set it as its metatable.
 | 
			
		||||
    -- Remember that `Animal.__index == Animal`.
 | 
			
		||||
    setmetatable(Cat, Animal)
 | 
			
		||||
 | 
			
		||||
    function Cat:new()
 | 
			
		||||
        -- Ultra-shorthand way of initializing a class instance!
 | 
			
		||||
        -- No need to declare any temporary locals, we can pass the table into `setmetatable`
 | 
			
		||||
        -- right away.
 | 
			
		||||
        return setmetatable({
 | 
			
		||||
            food = 1,
 | 
			
		||||
        }, self)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    -- Don't forget to override speak(), otherwise calling it will error out!
 | 
			
		||||
    function Cat:speak()
 | 
			
		||||
        print("meow")
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DCHD37G3WKEBJA5Z6"
 | 
			
		||||
        - note now that declaring `speak` _does not modify `Animal`_.
 | 
			
		||||
        for that, we would need to set the _`__newindex`_ metatable field on the `Animal`, not just `__index`.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D08QQ8XSXBRMCDV26"
 | 
			
		||||
    - now we can create instances of the `Cat`, and it will inherit the `feed` method from `Animal`:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local kitty = Cat:new()
 | 
			
		||||
    kitty:speak()
 | 
			
		||||
    kitty:feed()
 | 
			
		||||
    print(kitty.food) --> 2
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5DYZW7W7Q9H75HN8BM"
 | 
			
		||||
- ### generalising
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DTATR750GQ2S7EAPF"
 | 
			
		||||
    - with all this, we are now ready to pack this subclassing functionality into a nicer package.
 | 
			
		||||
    speaking of packages, let's create a module `class.lua`:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local Class = {}
 | 
			
		||||
    Class.__index = Class
 | 
			
		||||
 | 
			
		||||
    return Class
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DFTHS8P07T3KET5F8"
 | 
			
		||||
    - now, let's create a method for inheriting from the class.
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    -- insert above `return Class`
 | 
			
		||||
 | 
			
		||||
    function Class:inherit()
 | 
			
		||||
        local Subclass = {}
 | 
			
		||||
        Subclass.__index = Subclass
 | 
			
		||||
        -- Note how `self` in this instance is the parent class, as we call the method like `SomeClass:inherit()`.
 | 
			
		||||
        setmetatable(Subclass, self)
 | 
			
		||||
        return Subclass
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DTAK0EA4PR6JX2GV8"
 | 
			
		||||
    - this is going to let us cleanly inherit from classes, without needing to copy and paste all the `__index` and `setmetatable` boilerplate:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local Class = require "class"
 | 
			
		||||
    local Sub = Class:inherit()
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DKY7ZFWA42D9NJ49C"
 | 
			
		||||
    - the other boilerplaty bit was initialisation, so let's take care of that:
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    -- insert below the `end` of `function Class:inherit()`
 | 
			
		||||
 | 
			
		||||
    -- By default, let's make the base `Class` impossible to instantiate.
 | 
			
		||||
    -- This should catch bugs if a subclass forgets to override `initialize`. 
 | 
			
		||||
    function Class:initialize()
 | 
			
		||||
        error("this class cannot be initialized")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    -- `...` is Lua's notation for collecting a variable number of arguments
 | 
			
		||||
    function Class:new(...)
 | 
			
		||||
        local instance = {}
 | 
			
		||||
        -- `self` is the class we're instantiating, as this method is called like `MyClass:new()`
 | 
			
		||||
        setmetatable(instance, self)
 | 
			
		||||
        -- We pass the instance to the class's `initialize()` method, along with all the arguments
 | 
			
		||||
        -- we received in `new()`.
 | 
			
		||||
        self.initialize(instance, ...)
 | 
			
		||||
        return instance
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D8VD9E0SXCY337D3W"
 | 
			
		||||
    - having that, we can now rewrite our `Animal` example to use our super-simple class library.
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local Class = require "class"
 | 
			
		||||
 | 
			
		||||
    ---
 | 
			
		||||
 | 
			
		||||
    local Animal = Class:inherit()
 | 
			
		||||
 | 
			
		||||
    -- We'll provide a convenience function for implementers, for initialising the food value,
 | 
			
		||||
    -- as well as any other base fields that may come up.
 | 
			
		||||
    function Animal:_initialize()
 | 
			
		||||
        self.food = 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function Animal:speak()
 | 
			
		||||
        error("unimplemented")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function Animal:feed()
 | 
			
		||||
        self.food = self.food + 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    ---
 | 
			
		||||
 | 
			
		||||
    local Cat = Animal:inherit()
 | 
			
		||||
 | 
			
		||||
    -- Don't forget that our initialize() method errors by default, so it has to be overridden.
 | 
			
		||||
    function Cat:initialize()
 | 
			
		||||
        self:_initialize()
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function Cat:speak()
 | 
			
		||||
        print("meow")
 | 
			
		||||
    end
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DFS98T9X8A8SPYXCT"
 | 
			
		||||
    - having a nice class library like this makes things a lot more convenient.
 | 
			
		||||
    no longer do we have to mess with raw metatables!
 | 
			
		||||
    all we need to do is call `inherit()` or `new()`, and the magic is done for us.
 | 
			
		||||
 | 
			
		||||
    ```lua
 | 
			
		||||
    local kitty = Cat:new()
 | 
			
		||||
    kitty:speak()
 | 
			
		||||
    kitty:feed()
 | 
			
		||||
    print(kitty.food)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5DSR2Z0XGW6W2H97NH"
 | 
			
		||||
- ### wrapping up
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DANR0QF460EHKAWMP"
 | 
			
		||||
    - if you followed this tutorial from beginning to end, you now have a simple library for object-oriented programming in Lua, which supports creating classes, and inheriting from them.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DMCXV1VT37Z8M7P5G"
 | 
			
		||||
    - to further your understanding, you may want to think about the following:
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DRF6RVFG4P9XYDXZZ"
 | 
			
		||||
        - how would you call the superclass's implementation of a method?
 | 
			
		||||
        can you think of ways to make it convenient and easy to remember?
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DQW7G1HDWYGX4WEYC"
 | 
			
		||||
        - our class library implements a Ruby-style `Object:new(args)` function for constructing new instances of our class.
 | 
			
		||||
        Python however, uses the syntax `Object(args)` for constructing instances of objects. 
 | 
			
		||||
        can you think of a way to make our class library use the Python-style syntax?
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5D7QN029A3F1B6SYC2"
 | 
			
		||||
        - define a 2D vector class using our class library.
 | 
			
		||||
        can you think of a way to make use of Lua's native `+`, `-`, `*`, `/` math operators instead of named methods `:add()`, `:sub()`, `:mul()`, `:div()`?
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DJP0C1V8K2BQBD6G2"
 | 
			
		||||
        - try implementing an `object:instanceof(Class)` function, which checks that an object instance inherits from a given class.
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQHG5DN41D9BME6JXJ3R86"
 | 
			
		||||
        - Lua is a minimalistic, multi-paradigm language.
 | 
			
		||||
        can you think of any benefits and drawbacks towards doing object-oriented programming in Lua?
 | 
			
		||||
 | 
			
		||||
            % id = "01JKKQHG5DNRKVQJAVN4KP608R"
 | 
			
		||||
            - what are some problems for which this style of programming would lend itself as particularly good?
 | 
			
		||||
 | 
			
		||||
            % id = "01JKKQHG5D6TY9QSCQGEQWMZGM"
 | 
			
		||||
            - and similarly, what are some areas in which this style might not work so well?
 | 
			
		||||
 | 
			
		||||
% id = "01JKKQHG5DFNRGD6D17RHB3S22"
 | 
			
		||||
- ### further reading
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DDB411SKNFA8M7K2B"
 | 
			
		||||
    - you may wanna check these out for additional reference.
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5DRWGGE7GE3GMCB7E8"
 | 
			
		||||
    - [the Lua documentation on metatables](https://www.lua.org/manual/5.4/manual.html#2.4)---there's lots of other operators you can overload!
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQHG5D3PQZY439H61FN27V"
 | 
			
		||||
    - [rxi's `classic`](https://github.com/rxi/classic/blob/master/classic.lua) module---it's an example of a good, but small class library that has all the features you'd ever need.
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,26 @@ if you've been wondering what I've been up to, you've come to the right place.
 | 
			
		|||
        if you want to read any of the posts, follow the links.
 | 
			
		||||
        it's like that by design.
 | 
			
		||||
 | 
			
		||||
% tags = ["programming", "lua"]
 | 
			
		||||
  id = "01JKKQZRSG5ZRNH530D75E2660"
 | 
			
		||||
- ### [implementing classes in Lua][page:programming/lua/classes]
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQZRSGXT99FJ7V3BGKE2D5"
 | 
			
		||||
    - one of my absolutely favourite parts of Lua is how tiny, but capable it is.
 | 
			
		||||
    did you know you could implement object-oriented programming without needing any additional syntactic support?
 | 
			
		||||
 | 
			
		||||
    % id = "01JKKQZRSGMN81PXJWGP5Y17WY"
 | 
			
		||||
    - this is a remaster of an [old tutorial I published as a Gist](https://gist.github.com/liquidev/3f37f94efdacd14a654a4bdc37c8008f) to explain how object-oriented programming works in Lua to someone on the [LÖVE](https://love2d.org/) Discord server.
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQZRSGMW486TGSXQVM8FV3"
 | 
			
		||||
        - thus there's a high likelihood you've never read it.
 | 
			
		||||
        however, I think it's a pretty nice tutorial, so I'm republishing it here outside the shackles of GitHub.
 | 
			
		||||
 | 
			
		||||
        % id = "01JKKQZRSGNA5NRXR5H6WPZKWA"
 | 
			
		||||
        - and the programming trickery in it might just open your third eye a bit, so you should read it even if you're not into Lua!
 | 
			
		||||
 | 
			
		||||
% id = "01JK5SN2ZBDZTFZ27J3KNT4SQV"
 | 
			
		||||
  tags = ["music"]
 | 
			
		||||
- ### [Floating Points - Tilt Shift / Ablaze][page:music/tilt-shift-ablaze]
 | 
			
		||||
 | 
			
		||||
    % id = "01JK5SNYKRK08F5DJM4JGBK4C4"
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +57,7 @@ if you've been wondering what I've been up to, you've come to the right place.
 | 
			
		|||
    so that I can remember, and so the world can see, too...
 | 
			
		||||
 | 
			
		||||
% id = "01JHXVRT2HR6TXC2V9JG2XTZVB"
 | 
			
		||||
  tags = ["music"]
 | 
			
		||||
- ### [The Flashbulb - Flacks / aBliss][page:music/flacks]
 | 
			
		||||
 | 
			
		||||
    % id = "01JHXVRT2H2CTGBEDYWCMDTTS3"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@
 | 
			
		|||
        },
 | 
			
		||||
        { "regex": "0[xX]\\.[0-9a-fA-F]+([pP][-+]?[0-9]+)?", "is": "literal" },
 | 
			
		||||
        {
 | 
			
		||||
            "regex": "[0-9][0-9_]+(\\.[0-9_]*([eE][-+]?[0-9_]+)?)?",
 | 
			
		||||
            "regex": "[0-9]+(\\.[0-9]*([eE][-+]?[0-9]+)?)?",
 | 
			
		||||
            "is": "literal"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,7 @@
 | 
			
		|||
            }
 | 
			
		||||
        },
 | 
			
		||||
        { "regex": "\\.\\.\\.", "is": "punct" },
 | 
			
		||||
        { "regex": "[+=/*^%#<>~.-]+", "is": "operator" },
 | 
			
		||||
        { "regex": "[+=/*^%#<>~.:-]+", "is": "operator" },
 | 
			
		||||
        {
 | 
			
		||||
            "regex": "([a-zA-Z_][a-zA-Z0-9_]*)\\(",
 | 
			
		||||
            "is": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue