%% 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.