Good hierarchy implementation and practice

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
sherpal
Prole
Posts: 17
Joined: Wed Sep 14, 2016 9:29 am
Location: Belgium

Good hierarchy implementation and practice

Post by sherpal »

Hello there,

Probably this topic has been discussed many times, but I could not find a thread about it on the forums here. Hence creating a new one.

I'm currently implementing my own gui library, and have decided to use a class system with a full hierarchy of widgets (having started Lua through WoW addons, I took as inspiration the hierarchy described in http://wowprogramming.com/docs/widgets_hierarchy). Since I'm not an expert in anything, I just took the simple class implementation described in the Lua manual, and I basically copy-pasted the function defined here: https://www.lua.org/pil/16.3.html

I'm going to describe how I use it, and I would like to know if this is the right way, or if this is completely wrong and I should change what I'm doing. Basically properties of objects are "private", and I use methods like gets and sets in order to access them. I like this because I can check in the "set" method if everything is correct, and I like the get thing because it allow easy default value. However, it has been drawn to my attention that the way I'm doing this would be very bad in JavaScript. Of course, comparison is not reason, but maybe the way it works is similar in Lua, and in particular in LuaJIT, so I prefer to be prudent and ask it to you experts around here :D

So let's take an example with a Rectangle class. An object of that class would have properties width and height, and a method to compute the area of the rectangle. The Rectangle class is created via the createClass from the Lua manual, and here is how I do this.

Code: Select all

Rectangle = createClass()

function Rectangle:setWidth(w)
	assert(type(w) == "nil" or (type(w) == "number" and w > 0), "Width must be a positive number or nil.")
	self.width = w
end

function Rectangle:getWidth()
	return self.width or 3 -- default width is 3
end

function Rectangle:setHeight(h)
	assert(type(h) == "nil" or (type(h) == "number" and h > 0), "Height must be a positive number or nil.")
	self.height = h
end

function Rectangle:getHeight(h)
	return self.height or 2 -- default height is 2
end

function Rectangle:getArea()
	return self:getWidth() * self:getHeight()
end
A new object would then be created via

Code: Select all

r = Rectangle:new({})
r:setWidth(5)
print(r:getArea()) -- should be 10
I could for example create a class CurlyRectangle that would inherit from Rectangle, and with properties specifying how corner are smoothened.

Question is: is this ok, or is this very bad?

As a side question, I was also wondering if it would be a good idea to implement the widget hierarchy in C++ and then use it via FFI. As I've never touch to FFI, if you think it would be a better idea, could you reference me a good starting tutorial to do this?

Thank you very much for having taken the time to read me.

TLDR: what is good inheritance and class usage practice in Lua?
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Good hierarchy implementation and practice

Post by raidho36 »

Lua doesn't have classes so all of that is moot. Also, Lua is a "there are many ways to do this" kind of language. It's not a bad implementation unless you're doing triple backflips through ablaze hoops to open a tin can.

LuaJIT is already fast enough for you not to go out of your way to use native code instead, provided results will not be too good anyway since you're not good with it.

If you intend on using FFI libraries, go with C instead. It's FAR easier to learn and the binary has to expose C call API anyway. FWIW Lua itself is written in C.
User avatar
ivan
Party member
Posts: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Good hierarchy implementation and practice

Post by ivan »

raidho is correct, there is no "standard" way to write object-oriented code in Lua (although using metatables is almost always the most efficient approach).
Question is: is this ok, or is this very bad?
Looking at your code, it's not a bad start... to be honest, it looks a little clunky from a Lua perspective.
I don't see why it's necessary to assert every argument each time.
Basically properties of objects are "private"
Private properties/functions are a C++ feature that allows you to catch errors during compile-time. In Lua, you would only get a runtime error which you can't recover from. It's possible to implement that feature in Lua but it's just not very useful.
On the other hand, inheritance is good because it allows you to reuse the code.
what is good inheritance and class usage practice in Lua?
The simpler your code, the better.
If you only want OO with single-inheritance you can use the following:

Code: Select all

local oo = {}

local meta = {}
local rmeta = {}

-- Creates a new class
function oo.class()
  local c = {}
  local mt = { __index = c }
  meta[c] = mt
  rmeta[mt] = c
  return c
end

-- Creates a subclass
function oo.subclass(p)
  assert(meta[p], "undefined parent class")
  local c = oo.class()
  return setmetatable(c, meta[p])
end

-- Creates a new instance
function oo.instance(c)
  assert(meta[c], "undefined class")
  return setmetatable({}, meta[c])
end

-- Gets the class of an instance
function oo.getclass(i)
  local mt = getmetatable(i)
  return rmeta[mt]
end

return oo
Usage

Code: Select all

-- Inheritance:

local oo = require 'oo'

local Fruit = oo.class()
Fruit.sweetness_threshold = 5 -- sort of like "static"
function Fruit:initialize(sweetness)
  self.sweetness = sweetness
end
function Fruit:isSweet()
  return self.sweetness > Fruit.sweetness_threshold
end

local Lemon = oo.subclass(Fruit) -- subclassing
function Lemon:initialize()
  Fruit.initialize(self, 1) -- manually invoking the superclass' initializer
end

-- Instance:

local lemon = oo.instance(Lemon)
lemon:initialize()
print(lemon:isSweet()) -- false
If you want to have multiple inheritance and constructors there are more sophisticated libs out there (with mixin support) but I don't think the added complexity is worth it.
As a side question, I was also wondering if it would be a good idea to implement the widget hierarchy in C++ and then use it via FFI
I would advise against doing this - the point of Love2D is to make Lua scripts that can work on any platform. So you don't want to be writing platform-dependent code. Plus there are native GUI libraries out there that can be binded with Lua.
User avatar
sherpal
Prole
Posts: 17
Joined: Wed Sep 14, 2016 9:29 am
Location: Belgium

Re: Good hierarchy implementation and practice

Post by sherpal »

Thank you for the answers. At least you don't seem to be against it :)
ivan wrote:
Basically properties of objects are "private"
Private properties/functions are a C++ feature that allows you to catch errors during compile-time. In Lua, you would only get a runtime error which you can't recover from. It's possible to implement that feature in Lua but it's just not very useful.
Well, what I meant by "private" is that I don't want to write something like obj.prop = something, but I rather want to go through a "setProp" method, in order to go with the assert you don't seem to like :) I like being able to check that the property I have satisfy the conditions I want, so that I don't postpone the place where the error will appear.
raidho36 wrote:LuaJIT is already fast enough for you not to go out of your way to use native code instead, provided results will not be too good anyway since you're not good with it.
That was what I had in mind, but maybe there were a real advantage by doing so. Also, it would have forced me to learn :p
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 10 guests