Page 9 of 25

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Mon Nov 01, 2010 8:08 pm
by kikito
I've just finished changing the Callbacks module. Now it feels more 'right'.
  • I've split addCallback into two methods: before and after. I'm hoping this leans to a cleaner syntax, and people get less confused.
  • Callbacks now are active 'by default'. If you define foo and add a callback to it, the callback will be executed when you do obj:foo(). If you don't wish this to happen, you must call obj:fooWithoutCallbacks() instead
So our Human now looks like this:

Code: Select all

-- create a regular class, with two methods
Human = class('Human'):include(Callbacks) -- add the Callbacks module here
function Human:brushTeeth() print('Brushing teeth') end
function Human:sleep() print('ZZZ') end

-- insert two callbacks, before and after sleep
Human:before('sleep', 'brushTeeth')
Human:after('sleep', function() print('I got you, babe') end)

local peter = Human:new()
peter:sleep() -- normal call, prints 'Brushing teeth', 'ZZZ' and then 'I got you, babe'
peter:sleepWithoutCallbacks() -- call without callbacks, prints 'ZZZ'
Note:
  • initialize and destroy are not "special" any more; they behave like any other method. Just beware that YourClass.new allways calls initialize, and never calls initializeWithoutCallbacks, unless you modify it somehow.
  • Also, beware that when you do a before-initialize stuff, you will be dealing with objects not completely initialized. It's ok to do things like registering them on a list of instances, but attempting other things (using stuff that hasn't been initialized yet) can result in errors. Use after-initialize callbacks for that.

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Mon Nov 01, 2010 9:19 pm
by Robin
Great!

I still think ..WithoutCallbacks is a bit ugly. However, I couldn't come up with an alternative (at least not one which wouldn't add a large amount of complexity to Callbacks.lua).

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Tue Nov 02, 2010 11:59 pm
by kikito
Another small update:

I was tired of re-implementing again and again the same methods on my tree-like structures (setParent, getChildren, etc) and I decided to put all the tree-like behaviour on another middleclass-extras mixin called Branchy.

Each instance of a Branchy class will allways have a parent (can be nil) and a children list (self.children, can be empty but not nil). AddChild is used for tree construction:

Code: Select all

Tree = class('Tree'):include(Branchy)
--[[ Structure:
       root
       / \
      a1 a2
     / \
    b1 b2
]]
local root = Tree:new('root')
local a1 = root:addChild(Tree:new('a1'))
local a2 = root:addChild(Tree:new('a2'))
local b1 = a1:addChild(Tree:new('b1'), 'b1')
local b2 = a1:addChild(Tree:new('b2'), 'b2')
I've included other useful methods like getAncestors(), getDescendants() or getDepth().

Code: Select all

b1:getAncestors() -- {a1, root}
root:getDescendants() -- {a1,b1,b2,a2}
b2:getDepth() -- 2
I've also included 4 'apply-like' methods to save you some typing when applying treatments on the children/desdendants of a node. Two of them are 'sorted' versions of the other two.

As usual, the new mixin comes with lots of tests to make sure it works properly.

Please let me know if you are missing any important method.

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Thu Nov 04, 2010 3:40 pm
by TechnoCat
How are static class variables done?
I would like to set variables inside an object and then have all objects that are child objects (inherited the parent) to have the parent's static variables defined globally among the children objects.

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Fri Nov 05, 2010 6:55 am
by kikito
TechnoCat wrote:How are static class variables done?
Class variables are very straightforward:

Code: Select all

Sauce = class('Sauce')

Sauce.DEFAULT_INTENSITY = 10 -- class variable

function Sauce:initialize(intensity)
  super.initialize(self)
  self.intensity = intensity or self.DEFAULT_INTENSITY
end

local pickle = Sauce:new() -- 10
local dijon = Sauce:new(80)

Class variables are also conserved between subclasses (SoftSauce, a subclass of Sauce also has a DEFAULT_INTENSITY) and they can be overridden (you can do SoftSauce.DEFAULT_INTENSITY = 5)

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Fri Nov 05, 2010 7:21 am
by Robin
I'm not very familiar with middleclass, but can't you do:

Code: Select all

Sauce = class('Sauce')

Sauce.intensity = 10 -- class variable

function Sauce:initialize(intensity)
  super.initialize(self)
end

local pickle = Sauce:new() -- 10
local dijon = Sauce:new(80)
?

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Fri Nov 05, 2010 9:51 am
by kikito
Well, you can, but dijon will not have an 80 intensity (I think you are missing one line on the initializer)

But I don't like that very much; I prefer having default values set up on the initializer. It's one line longer, but it is more explicit. Using the class variable like that feels a bit "too magical" for my taste. In fact, I actually tend to use the class variables like this: MyClass.variable instead of myInstance.variable, just to make it more explicit.

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Fri Nov 05, 2010 11:51 am
by Robin
kikito wrote:Well, you can, but dijon will not have an 80 intensity (I think you are missing one line on the initializer)
Whoops. What I meant was:

Code: Select all

Sauce = class('Sauce')

Sauce.intensity = 10 -- class variable

function Sauce:initialize(intensity)
  super.initialize(self)
  self.intensity = intensity or self.intensity
end

local pickle = Sauce:new() -- 10
local dijon = Sauce:new(80)
kikito wrote:Using the class variable like that feels a bit "too magical" for my taste.
Why is it magical? I'd say it's less magical than having an extra DEFAULT_INTENSITY.

MyClass.variable versus myInstance.variable can be debated. I actually prefer the latter.

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Fri Nov 05, 2010 12:07 pm
by TechnoCat
Robin wrote:

Code: Select all

Sauce = class('Sauce')

Sauce.intensity = 10 -- class variable

function Sauce:initialize(intensity)
  super.initialize(self)
  self.intensity = intensity or self.intensity
end

local pickle = Sauce:new() -- 10
local dijon = Sauce:new(80)
kikito wrote:

Code: Select all

Sauce = class('Sauce')

Sauce.DEFAULT_INTENSITY = 10 -- class variable

function Sauce:initialize(intensity)
  super.initialize(self)
  self.intensity = intensity or self.DEFAULT_INTENSITY
end

local pickle = Sauce:new() -- 10
local dijon = Sauce:new(80)
What is the difference between these two? Other than capitalization?

Re: middleclass & middleclass-extras: OOP for LUA

Posted: Fri Nov 05, 2010 1:58 pm
by kikito
TechnoCat wrote:What is the difference between these two? Other than capitalization?
None. I (subconsciously) choose an example that doesn't really work very well for what Robin (and maybe you?, I'm not sure yet) was proposing.

Which is, basically, specifying "default values for instances" by creating a class variable.
Robin wrote:Why is it magical? I'd say it's less magical than having an extra DEFAULT_INTENSITY.
For two reasons.

First, I kind of expect all the instance.variables to be defined on the initializer. The class variable can be defined very far away from the initializer. It can be inherited from a superclass.
You might argue that initialization is already "spread" when you have superclasses (there's a 'chain' of super.initialize calls) but at least it is explicit - you start on an initializer and there's (should be) a line right there telling you "this method also calls the initialize method on the superclass". With the class variable this can be 'hidden' somewhere else.

Second, it is a "ghost atribute". Think about this:

Code: Select all

for k,v in pairs(pickle) print(k, '=>', v) end
If you explicitly did self.intensity = ... on the initializer, you will get 'intensity' printed out. But it will not be printed out if you just put 'intensity' as a class variable. It could be kind of inconsistent, too: depending on the implementation, some instances would print 'instance' and others wouldn't.

The only benefit is that you get rid of one line on the initializer. That's a price I'm personally willing to pay in order to avoid the two situations above.

In addition to this, on middleclass there's the assumption that 'methods are in classes, and attributes are in instances'. It's not currently used or enforced (I think) on middleclass-extras, but it might in the future (think serialization). Instances using class variables like that are less future-proof.
Robin wrote:MyClass.variable versus myInstance.variable can be debated. I actually prefer the latter.
Me too. When it refers to an instance variable.

What I don't like is referring to a class variable with the notation instance.variable. For the reasons above.