📜 ⬆️ ⬇️

Classes in lua, or getting rid of colon

As everyone knows, lua does not have any such classes or objects. However, there are metatables and syntactic sugar.
With the help of these mechanisms, it is sufficient to simply implement the similarity of classes.
The result is something like this:
The easiest class
local MyClass = {} -- the table representing the class, which will double as the metatable for the instances MyClass.__index = MyClass -- failed table lookups on the instances should fallback to the class table, to get methods -- syntax equivalent to "MyClass.new = function..." function MyClass.new(init) local self = setmetatable({}, MyClass) self.value = init return self end function MyClass.set_value(self, newval) self.value = newval end function MyClass.get_value(self) return self.value end local i = MyClass.new(5) -- tbl:name(arg) is a shortcut for tbl.name(tbl, arg), except tbl is evaluated only once print(i:get_value()) --> 5 i:set_value(6) print(i:get_value()) --> 6 

(taken from lua-users.org/wiki/ObjectOrientationTutorial )

All this is of course good, even with a certain dexterity, inheritance can be realized ...
But where are the public and private members of the class? In fact, in this example, they are all public. Yes, and we must remember where to use the colon:
 MyClass:myFunc() 

where just one point:
 MyClass.myOtherFunc() 

What about static class members? Do have to refuse?


So I did not want to refuse, and began to collective farm ...

So, I present to you my farm:
My collective farm
 createClass = function() local creator = {} creator.__private = { object_class = {}, } creator.__oncall = function(class_creator) -- Get the class definition so we can make needed variables private, static, etc. local this = class_creator() -- Initialize class from class definition __init = function() -- Init Public Static local class = {} if (type(this.__public_static) == "table") then class = this.__public_static end -- Init Object local thisClass = this local __constructor = function(...) local object = {} local this = class_creator() -- Init Public if (type(this.__public) == "table") then object = this.__public end -- Init static values of the class this.__public_static = thisClass.__public_static this.__private_static = thisClass.__private_static -- Call Constructor if (type(this.__construct) == "function") then this.__construct(...) end -- Returning constructed object return object end return {class = class, constructor = __constructor} end -- Creating class (returning constructor) local class_data = __init() local class = class_data.class local constructor = class_data.constructor -- Set class metatable (with setting constructor) local class_metatable = { __newindex = function(t, key, value) if type(t[key])=="nil" or type(t[key])=="function" then error("Attempt to redefine class") end rawset(t, key, value) end, __metatable = false, __call = function(t, ...) if type(t) == nil then error("Class object create failed!") end local obj = constructor(...) creator.__private.object_class[obj] = t local object_metatable = { __newindex = function(t, key, value) class = creator.__private.object_class[t] if type(class[key])~="nil" and type(class[key])~="function" then rawset(class, key, value) return end if type(t[key])~="nil" and type(t[key])~="function" then rawset(t, key, value) return end error("Attempt to redefine object") end, __index = t, __metatable = false, } setmetatable(obj, object_metatable) return obj end, } -- Setting class metatable to the class itself setmetatable(class, class_metatable) -- Returning resulting class return class end return creator.__oncall end createClass = createClass() 


')
And how to use? Very simple, here's a template for you:
 myclass_prototype = function() local this = {} this.__public_static = { -- Public Static Variables statvalue = 5, -- Public Static Funcs statfunc = function() print(this.__public_static.statvalue) end, } this.__private_static = { -- Private Static Variables privstatdat = 2, -- Private Static Funcs privstatfunc = function() print(this.__private_static.privstatdat) end, } this.__public = { -- Public Variables pubdata = 3, -- Public Funcs pubfunc = function(newprivate) print(this.__public.pubdata) this.__private.privdata = newprivate end, } this.__private = { -- Private Variables privdata = 1, -- Private Funcs listallprivate = function() print(this.__private.privdata) end, } this.__construct = function() -- Constructor end return this end myclass=createClass(myclass_prototype) 

As you can see, each time you call from inside the class, you will have to specify the path each time, a for “this .__ private.privdata”, but here is an example of using the created class!
 myobject = myclass() myobject.pubfunc(999) 

Calling this code will create a myobject object from the class myclass, and the pubfunc function will be called, which will highlight the contents of the public variable and change the private one.
And no problems with colons!
By the way, static calls also work. Both from a class, and from object.

So, briefly tell you what kind of magic happens here. And here comes the juggling of the so-called upvalues. upvalues ​​are variables that are visible from the inside, but not from the outside! Very similar to private, isn't it?
So, having created the “prototype” function, we created a new scope, and placed all the insides of our class in it, bringing out only public and public static class members. And the rest of the magic is performed by metatables, which allow us to determine what exactly will happen when a “non-existent” member of an external table is requested, which represents our class / object.
Sounds messy, I know, but I can't explain it better - not special :)

I thought for a long time how to do inheritance with such a system, but I never thought of it - upvalues ​​seriously limits our actions, and did not want to use perversions like the debug library - it is not included everywhere. If anyone thinks, I will be glad to see!

PS: if for someone my post is something obvious - well, it means I overestimated myself :) Do not judge strictly, maybe this decision will be useful to someone for some reason!

Source: https://habr.com/ru/post/182018/


All Articles