## How to do Methods and Inheritance in Standard Lua Standard Lua has the ability to do classes and inheritance. Let me explain how. Lua has a colon operator, for method lookup. It looks like this: ```lua local result = v1:dotproduct(v2) ``` By default, this looks for the method "dotproduct" inside the table v1. But you don't really want to put a whole bunch of function objects (like dotproduct) into v1, and into every other vector. Instead, you want to put those function objects in a separate table, the class table: ```lua vector = {} function vector.dotproduct(v1, v2) ... end function vector.crossproduct(v1, v2) ... end ``` Fortunately, Lua can do that. Lua has a metamethod, INDEX, that basically says: "if you don't find what you're looking for in this table, look in that table instead." We can use the INDEX metamethod to tell lua: if you're looking a method "dotproduct" in v1, and it's not there, look in class vector instead. So to do that, you only need two steps. First, we're going to decide that class vector isn't just hold the methods for vectors. We're also going to use it as the metatable for all vectors: ```lua v1 = {x=100, y=100, z=100} v2 = {x=200, y=200, z=200} setmetatable(v1, vector) setmetatable(v2, vector) ``` Now that vector is the metatable for all vectors, you can put metamethods into class vector and they will affect all vectors. We're going to put an __INDEX metamethod in there: ```lua vector = {} vector.__INDEX = vector function vector.dotproduct(v1, v2) ... end function vector.crossproduct(v1, v2) ... end ``` That says: if you're doing a lookup on a vector, and the lookup fails, look in class vector instead. OK, in case you didn't quite follow, let me walk you through step by step what happens when you evaluate the expression v1:dotproduct(v2). First, the colon operator looks for "dotproduct" in v1. It's not going to find it: we're not going to put all those function objects into every single vector. When the table lookup code fails to find "dotproduct" in v1, it goes to a fallback codepath: it checks whether v1 has a metatable. It does: vector is the metatable for all vectors. So v1 does indeed have a metatable. The table lookup code then checks whether the metatable contains INDEX: again, it does, class vector contains INDEX. Finally, the table lookup checks what INDEX is pointing to: class vector again. So the table lookup code, having failed to find "dotproduct" in v1, looks for it in class vector. The method lookup succeeds. The little trick "vector.__INDEX = vector" certainly feels arcane, but it works. I like to hide it with a little syntactic sugar: ```lua def makeclass(name) _G[name] = {} _G[name].__INDEX = _G[name] end ``` Now I can say this, and it hides all the metatable magic: ```lua makeclass("vector") function vector.dotproduct(v1, v2) ... end function vector.crossproduct(v1, v2) ... end ``` A useful property of this design is that vector is *both* the metatable for all vectors, and *also* the class table for all vectors. One consequence of that is that I can put both regular methods and metamethods in there: ```lua makeclass("vector") function vector.__eq(v1, v2) ... end function vector.dotproduct(v1, v2) ... end function vector.crossproduct(v1, v2) ... end ``` Let's say you also want inheritance: you want vector to inherit from vectorbase. That's another thing you can accomplish using INDEX again: "if you can't find what you're looking for in vector, look in vectorbase." Here's the code for that: ```lua makeclass("vectorbase") function vectorbase.whatever(...) ... end makeclass("vector") function vector.dotproduct(v1, v2) ... end function vector.crossproduct(v1, v2) ... end setmetatable(vector, vectorbase) ``` That 'setmetatable' directs all failed lookups from vector into vectorbase. I've set up a search path. That's really all there is to it. Honestly, it's reasonably straightforward. ## Why I Don't Love It: High Bug Potential Let's say you want to create a class, "json". This is for receiving and transmitting json over HTTP: ```lua json={} function json.validate() ... end function json.serialize(output) ... end ``` Now, suppose you receive this json from an HTTP client: ```json { "jsonrpc" : "2.0", "validate" : true } ``` You convert the json into a lua table *request*, call setclass(request, json), and then you try to validate the request: ```lua bool legal = request:validate() ``` This produces an error: "true" is not a callable function. That's because when it looks for the "validate" method, it instead finds the "validate" data that is in the request. The underlying problem is this: method lookups should *only* look in the class table, not in the request. Meanwhile, data lookup should be *only* in the request, not in the class table. But the INDEX metamethod doesn't differentiate between method lookups and data lookups. All lookups are treated the same. So all accesses go to *both* tables. Using this design for object-orientation means you're always at risk of data hiding your methods, at unexpected times. It also means you can get false readings on instance variables if the actual instance variable is nil. ## Why I Don't Love It: Speed How much does this cost: ```lua local result = v1:dotproduct(v2) ``` The answer is: it has to do three table lookups for just the method lookup: - v1["dotproduct"] - metatable["__INDEX"] - vector["dotproduct"] That also applies to data lookups. Suppose I do this: ```lua local param = request.param ``` That's a simple data lookup. But if the request is of class json, and the request doesn't contain "param", then it does three table lookups, trying to find the data "param": - request["param"] - metatable["__INDEX"] - json["param"] Looking for param in the class table, aside from being risky, is wasting CPU time. This makes lua, an already slow interpreted language, significantly slower. ## A Patch To solve these problems, we need to establish a rule: method lookups should *only* go to the class table, and data lookups should *only* go to the data table. To make that happen, I introduce a new metamethod, CLASS (with leading underscores). This metamethod changes *only* the behavior of the method look up operator (colon): ```lua v1:dotproduct(v2) ``` The behavior is: if you do a method lookup, then the colon operator looks to see if v1 has a metatable with the CLASS metamethod in it. If so, then it looks for the method in the *metatable* of v1 instead of looking in v1. Let me show you how this is intended to be used. We create the class table in pretty much the same way, but instead of putting INDEX in there, we put CLASS: ```lua vector = {} vector.__CLASS = true function vector.dotproduct(v1, v2) ... end function vector.crossproduct(v1, v2) ... end ``` We again intend to make vector the metatable for all vectors. So again, when you create a vector, you do this: ```lua v1 = {x=100, y=100, z=100} v2 = {x=200, y=200, z=200} setmetatable(v1, vector) setmetatable(v2, vector) ``` So far, it looks almost identical. But when you do v1:dotproduct(v2), the method lookup checks whether v1 has a metatable (it does), and whether that metatable has CLASS in it (it does). So therefore, it *doesn't* look for dotproduct in v1. It *only* looks for it in the metatable of v1, which is class vector. The CLASS metamethod has no effect on data lookups. That incudes the dot operator, and the array lookup operator: ```lua local param = request.param local param = request["param"] ``` Even if request above has the json class as its metatable, these lookups are just plain lua table lookups, and that's all. Class json isn't involved. This cleanly separates the two namespaces: data is looked up in the data table, methods are looked up in the methods table. So that leaves inheritance. We use a slightly different version of the CLASS metamethod: ```lua vector = {} vector.__CLASS = vectorbase function vector.dotproduct(v1, v2) ... end function vector.crossproduct(v1, v2) ... end ``` Saying CLASS=true means "this is a class." Saying CLASS=vectorbase means "this is a class, derived from vectorbase". If you do that, then the method look-up operator will look in vector first, then vectorbase, and it will follow the inheritance chain upward. How does the CLASS metamethod compare, performance-wise, to using the INDEX metamethod to achieve object-orientation? Let's start with data lookups. Using INDEX, this can take up to three table lookups: ```lua local param = request.param ``` However, using CLASS instead, this is only ever one table lookup. You might assume that since request has a metatable, that lua would at least have to do a table lookup to *see* if there's an INDEX metamethod, even if there's not one. However, lua has a clever optimization: lua remembers that INDEX is not present in class json, and it doesn't look a second time. It only ever does the INDEX lookup once, for the entire program. Method lookups like v1:dotproduct(v2) are also accelerated. Using INDEX, it takes three table lookups to find "dotproduct" in class vector. Using CLASS, however, it usually takes only one table lookup. It skips looking in v1 entirely. And, like INDEX, lua remembers that CLASS=true is present in class vector, and it doesn't look a second time. So it goes straight to looking for "dotproduct" in class vector. It only has to lookup CLASS if the method is not found, and we have to walk the inheritance tree. Finally, I'd like to say something about readability: ```lua vector.__CLASS = true ``` ## Summary Metamethod CLASS achieves object-oriented method lookup, but with two advantages over using INDEX: * Safely separates the two namespaces: data, and methods. * CLASS is faster than using INDEX.