middleclass & extras: middleclass 3.0 is out!

Showcase your libraries, tools and other projects that help your fellow love users.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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.
When I write def I mean function.
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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).
Help us help you: attach a .love.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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.
When I write def I mean function.
User avatar
TechnoCat
Inner party member
Posts: 1611
Joined: Thu Jul 30, 2009 12:31 am
Location: Milwaukee, WI
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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)
When I write def I mean function.
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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)
?
Help us help you: attach a .love.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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.
When I write def I mean function.
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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.
Help us help you: attach a .love.
User avatar
TechnoCat
Inner party member
Posts: 1611
Joined: Thu Jul 30, 2009 12:31 am
Location: Milwaukee, WI
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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?
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & middleclass-extras: OOP for LUA

Post 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.
When I write def I mean function.
Post Reply

Who is online

Users browsing this forum: No registered users and 7 guests