Go Back   WowAce Forums > Addon Chat > Libraries
Libraries Threads for new libraries and mixins.

Reply
 
Thread Tools
Old 01-03-2009   #1
taleden
Senior Member
 
Join Date: Mar 2007
Posts: 257
Default LibOOP

LibOOP is a small, framework-independent library that provides easy-to-use object orientation in Lua. If you're still using AceOO-2, or if you've been rolling your own metatables to achieve object orientation, give LibOOP a try.

Main Page: http://www.wowace.com/projects/liboop
Getting Started: http://www.wowace.com/projects/liboo...etting-started
API: http://www.wowace.com/projects/liboop/pages/api

Please leave any feedback in this thread, or file tickets on the project page.

Last edited by taleden; 01-31-2009 at 10:57 PM. Reason: 1.0.0-beta
taleden is offline   Reply With Quote
Old 01-03-2009   #2
Adirelle
Legendary Member
 
Adirelle's Avatar
 
Join Date: Dec 2006
Posts: 2,386
Default Re: LibOOP

Some methods almost have the same name, e.g. extend() and extends(), that may be very confusing. I am not a fan of mixing upper and lower case method names too.

Then you have to add some code to have your library properly upgrading. ATM if a newer version of the library is loaded, all old classes are lost.

That is not hard though, e.g. for ooSuper, replace:
Code:
local ooSuper = setmetatable({}, weakKeys)
By:
Code:
if not LibOOP.ooSuper then
  LibOOP.ooSuper = setmetatable({}, weakKeys)
end
local ooSuper = LibOOP.ooSuper
And that is fine.
__________________
Author of AdiButtonAuras, AdiBags, Squire2 and several other addons.

Each time you hit your "copy" command with a block of code, think about a way to refactor it so it did what you want without using the "paste" command.
Adirelle is offline   Reply With Quote
Old 01-03-2009   #3
taleden
Senior Member
 
Join Date: Mar 2007
Posts: 257
Default Re: LibOOP

Quote:
Originally Posted by Adirelle View Post
Some methods almost have the same name, e.g. extend() and extends(), that may be very confusing. I am not a fan of mixing upper and lower case method names too.
What alternative names do you suggest? I couldn't think of anything equally descriptive, except maybe using "subclass" instead of "extend", but that kind of clashes with "superclass" which returns an existing thing rather than creating a new thing.

The case difference came from not wanting to have exactly the same names for the library-wrapped functions and the class/object-inherited-methods, since they take slightly different arguments. Maybe case consistency would be better, it just seemed convenient to me to have an easy distinction between the versions that require you explicitly pass the class/object, and the versions you call directly on the class/object.

Quote:
Originally Posted by Adirelle View Post
Then you have to add some code to have your library properly upgrading. ATM if a newer version of the library is loaded, all old classes are lost.
At the moment it's not quite as bad as losing all your classes, because all those tables that hold the OO infrastructure are accessed in closure, so if you already have classes/objects in one revision and then load a new revision, the old classes have inherited method pointers which are using the old infrastructure tables in closure. So you could still use {class|object}:{method}(...), you just couldn't use LibOOP:{method}({class|object}, ...).

But, yeah, that's on the list.
taleden is offline   Reply With Quote
Old 01-03-2009   #4
Adirelle
Legendary Member
 
Adirelle's Avatar
 
Join Date: Dec 2006
Posts: 2,386
Default Re: LibOOP

Quote:
Originally Posted by taleden View Post
What alternative names do you suggest? I couldn't think of anything equally descriptive, except maybe using "subclass" instead of "extend", but that kind of clashes with "superclass" which returns an existing thing rather than creating a new thing.

The case difference came from not wanting to have exactly the same names for the library-wrapped functions and the class/object-inherited-methods, since they take slightly different arguments. Maybe case consistency would be better, it just seemed convenient to me to have an easy distinction between the versions that require you explicitly pass the class/object, and the versions you call directly on the class/object.
Mixed case does not seem to be a convenient distinction to me. I find it more confusing that anything. At least, if the method had the same in the object and in the library, I could easily remembered that their signature are different. E.g. it makes more sense that the library-wrapped GetClass method expects an instance (LibOOP:GetClass(instance)) where the object-wrapped GetClass acts on the instance itself (instance:GetClass()).

Thus I would go with something like this:

LibOOP:Class(super) or super:extend()
=> LibOOP:NewClass(super) or super:NewClass()

LibOOP:Extends(class, super, direct) or class:extends(super, direct)
=> LibOOP:IsSubClassOf(class, super, direct) or class:IsSubClassOf(super, direct)

LibOOP:New(class, ...) or class:new(...) or class(...)
=> LibOOP:New(class, ...) or class:New(...) or class(...)

LibOOP:GetSuperClass(class) or class:superclass()
=> LibOOP:GetSuperClass(class) or class:GetSuperClass()

etc.

Hrm. Actually you do not need to reference the LibOOP itself, so you could ultimately have this:

LibOOP:GetSuperClass(class) or class:superclass()
=> LibOOP.GetSuperClass(class) or class:GetSuperClass()

This way you could use the very same function in LibOOP and class, e.g. :

Code:
local function class_GetSuperClass(self)
  -- return the superclass of self
end
baseClass.GetSuperClass = GetSuperClass
LibOOP.GetSuperClass = GetSuperClass
But I admit it might be a little confusing so I would not use it.
__________________
Author of AdiButtonAuras, AdiBags, Squire2 and several other addons.

Each time you hit your "copy" command with a block of code, think about a way to refactor it so it did what you want without using the "paste" command.
Adirelle is offline   Reply With Quote
Old 01-03-2009   #5
taleden
Senior Member
 
Join Date: Mar 2007
Posts: 257
Default Re: LibOOP

Quote:
Originally Posted by Adirelle View Post
Mixed case does not seem to be a convenient distinction to me. I find it more confusing that anything. At least, if the method had the same in the object and in the library, I could easily remembered that their signature are different. E.g. it makes more sense that the library-wrapped GetClass method expects an instance (LibOOP:GetClass(instance)) where the object-wrapped GetClass acts on the instance itself (instance:GetClass()).
Fair enough; I'll change that in the next beta.

Quote:
Originally Posted by Adirelle View Post
Thus I would go with something like this:

LibOOP:Class(super) or super:extend()
=> LibOOP:NewClass(super) or super:NewClass()

LibOOP:Extends(class, super, direct) or class:extends(super, direct)
=> LibOOP:IsSubClassOf(class, super, direct) or class:IsSubClassOf(super, direct)

LibOOP:New(class, ...) or class:new(...) or class(...)
=> LibOOP:New(class, ...) or class:New(...) or class(...)

LibOOP:GetSuperClass(class) or class:superclass()
=> LibOOP:GetSuperClass(class) or class:GetSuperClass()
I'll ponder this. I'm inclined to go for the shortest, most type-able syntax that still clearly reflects what's being done, and I feel like IsSubClassOf and to some extent GetSuperClass are somewhat cumbersome to type, but maybe I just need to get over that. It's probably worth having a look at the wording in natively OO languages for some more inspiration. But I'll accept that :extend and :extends are too similar.

Quote:
Originally Posted by Adirelle View Post
Hrm. Actually you do not need to reference the LibOOP itself, so you could ultimately have this:

LibOOP:GetSuperClass(class) or class:superclass()
=> LibOOP.GetSuperClass(class) or class:GetSuperClass()

This way you could use the very same function in LibOOP and class, e.g. :

Code:
local function class_GetSuperClass(self)
  -- return the superclass of self
end
baseClass.GetSuperClass = GetSuperClass
LibOOP.GetSuperClass = GetSuperClass
But I admit it might be a little confusing so I would not use it.
Yeah, I did think about that when I was first laying out the API, but decided that having wrapper functions that just tail-called the regular methods was preferable, because otherwise you'd have to remember to use method-calling syntax (obj:method()) in some cases and function-calling syntax (LibOOP.func()) in others, which would be worse. I preferred the consistency of everything being in method syntax, even though (as you point out) LibOOP's wrappers don't actually need a self-reference.

Actually, in the beginning the only method LibOOP provided was :Class(), because everything else was accessible via the classes/objects themselves. I added the other wrappers later as a safeguard against someone overriding the methods (i.e. if someone overrides :New on their class, then its impossible to instantiate it; if you re-use someone else's code that did this, then what can you do?).

On the other hand, maybe that should be allowed. I dunno. Real OO languages don't have this problem of course because all the infrastructure is managed with reserved keywords which can't be overridden in the first place.

So maybe the first question is, should LibOOP bother providing these methods at all? Or should it just point out that if you override the provided methods in your class, then you're explicitly disabling portions of the OO functionality.
taleden is offline   Reply With Quote
Old 01-03-2009   #6
Adirelle
Legendary Member
 
Adirelle's Avatar
 
Join Date: Dec 2006
Posts: 2,386
Default Re: LibOOP

Quote:
Originally Posted by taleden View Post
I'll ponder this. I'm inclined to go for the shortest, most type-able syntax that still clearly reflects what's being done, and I feel like IsSubClassOf and to some extent GetSuperClass are somewhat cumbersome to type, but maybe I just need to get over that. It's probably worth having a look at the wording in natively OO languages for some more inspiration.
I am more fan of method names being verbs, actions or phrases, where attributes are nouns. E.g. .class is an attribute where :GetClass() is the corresponding method.
__________________
Author of AdiButtonAuras, AdiBags, Squire2 and several other addons.

Each time you hit your "copy" command with a block of code, think about a way to refactor it so it did what you want without using the "paste" command.
Adirelle is offline   Reply With Quote
Old 01-06-2009   #7
taleden
Senior Member
 
Join Date: Mar 2007
Posts: 257
Default Re: LibOOP

I had a look at the syntax for a few other popular OO languages, and came up with this:

Code:
CLASS OPERATIONS           Ruby           PHP             Python      C++        Java

define                     class          class           class       class      class
extend                     class <        extends         class ()    class :    extends
called during extension    inherited                                             
forbid extension                          final                                  final
is extension forbidden                                                           
forbid overriding                         final                                  final
test direct extension                                                            isassignablefrom
test indirect extension    kind_of        is_subclass_of  issubclass             isassignablefrom
get superclass             superclass     parent          super                  getsuperclass
call inherited method      super          parent          super                  super


OBJECT OPERATIONS          Ruby           PHP             Python      C++        Java

create from class          new             new            <class>()   new        new
create from object         dup             clone                                 clone
called during creation     initialize      construct      init        <class>    <class>
called during destruction  finalizer       destruct                   ~<class>   finalize
called during cloning      initialize_new  clone                                 
forbid modification        freeze                                                
is modification forbidden  frozen                                                
test direct inheritance    instance_of                                           
test indirect inheritance  kind_of         instanceof     isinstance             instanceof
get object's class         class           get_class                             getclass
call inherited method      super           parent         super                  super
Note that this doesn't quite show exact syntax or usage; I was more interested in the wording or syntax terminology used for various functions.

I'm also starting to learn toward removing the redundancies in the API, so that the inherited class/object methods are the only way to instantiate/extend/etc. This way, overriding those methods becomes a way of implementing other features, such as giving classes a callback when they're extended, or coding a singleton.

On the other hand, things like instanceof and super probably shouldn't be disable-able or override-able. I'm not sure yet whether to try to circumvent that by providing those via the library (instead of via inherited methods), or enforce it by tweaking class/object metatables to forbid redefining those names, or just document that it's not recommended and leave it up to the developer. Comments welcome.
taleden is offline   Reply With Quote
Old 01-06-2009   #8
Adirelle
Legendary Member
 
Adirelle's Avatar
 
Join Date: Dec 2006
Posts: 2,386
Default Re: LibOOP

About instanceof: I think it is better out of the objects and classes. In Java, it is an operator. This allows to accept tests like "null instanceof someClass" or "1 instanceof someClass" without any issue neither error.

About calling overridden methods (super calls): something that should not be forgotten is that when you call overridden method, you do not want the method of the parent class of self but the method of the parent class of the class that defines the method.
This is a quite static information.

Imagine three classes A, B, C.
- B extends A,
- C extends A.
- A defines the method bark(),
- B overrides the method bark() ; the new method calls inherited method bark().

With an instance of C, C.bark() actually is B.bark() that calls A.bark().

If you tried to get super from self, B.bark() would end up calling B.bark() because C inherits from B and you had an infinite recursion loop.

Speaking of what, ATM I can cause infinite recursion with current LibOOP with the following code :

Code:
local classA = LibOOP:Class()
function classA.prototype:count()
  return 1
end

local classB = classA:extend()
function classB.prototype:count()
  return self:super("count") + 1
end

local classC = classB:extend()

local obj = classC:new()
print(obj:count()) -- infinite recursion
Fixed by:
Code:
function classB.prototype:count()
  return classA.prototype:count() + 1
end
__________________
Author of AdiButtonAuras, AdiBags, Squire2 and several other addons.

Each time you hit your "copy" command with a block of code, think about a way to refactor it so it did what you want without using the "paste" command.

Last edited by Adirelle; 01-06-2009 at 04:25 PM.
Adirelle is offline   Reply With Quote
Old 01-06-2009   #9
taleden
Senior Member
 
Join Date: Mar 2007
Posts: 257
Default Re: LibOOP

Quote:
Originally Posted by Adirelle View Post
About calling overridden methods (super calls): something that should not be forgotten is that when you call overridden method, you do not want the method of the parent class of self but the method of the parent class of the class that defines the method. This is a quite static information.
Ah, good catch; I didn't think that through carefully enough. This (and the recursion it can cause) will be fixed in 0.2.

However I'm not sure it's really "static" information, because Lua by its nature isn't very static. My hope with LibOOP was to do object orientation in a "Lua-ish" way, preserving as much of Lua's dynamic flavor as is practical. Part of that is the option of changing classes on the fly, and having those changes propagate automatically to subclasses and objects; I think that means the identity of a "super"-called method will have to be evaluated each time.


Here's my tentative API for v0.2. The changes are mostly motivated by this discussion, plus the addition of Clone (whose default inherited implementation will function like New followed by a shallow property copy).

Code:
define class: LibOOP:Class()
extend class: <class>:Extend()  overridable
get class' parent class: <class>:GetSuperClass()  non-overridable  //  LibOOP:GetSuperClass(ref)
test class inheritance: <class>:SubClassOf([class,[ direct]])  non-overridable  //  LibOOP:SubClassOf(ref[, class[, direct]])
call inherited class method: <class>:Super(method[, ...])  non-overridable

create object: <class>:New(...)  overridable
clone object: <obj>:Clone(...)  overridable
get object's class: <obj>:GetClass()  non-overridable  //  LibOOP:GetClass(ref)
test object inheritance: <obj>:InstanceOf([class[, direct]])  non-overridable  //  LibOOP:InstanceOf(ref[, class[, direct]])
call inherited object method: <obj>:Super(method[, ...])  non-overridable

Last edited by taleden; 01-06-2009 at 05:35 PM.
taleden is offline   Reply With Quote
Old 01-07-2009   #10
taleden
Senior Member
 
Join Date: Mar 2007
Posts: 257
Default Re: LibOOP

I hope to have 0.2-beta ready tonight or tomorrow. The changelog so far is:

Code:
- almost all methods have been renamed for better clarity and consistency
- now supports runtime-upgrades via LibStub
- the "Super" method of both classes and objects should now work when called via a subclass of the class which defined the method which calls Super()
- class methods "GetSuperClass", "SubClassOf" and "Super" (inherited from the base class) may no longer be overridden
- object methods "GetClass", "InstanceOf" and "Super" (inherited from the base prototype) may no longer be overridden
- constructors are now implemented by overriding the class' "New" method; the object method name "__init" no longer has any special meaning (however, in general, method names beginning with two underscores are still reserved for future magic)
- objects now inherit a basic "Clone" method from the base prototype
- the library method "Class" no longer accepts an argument; to extend an existing class, use "<class>:Extend()"
- the library method "New" is removed; to instantiate a class, use "<class>:New()" or "<class>()"
- library methods will accept arguments which are not classes or objects, and will usually return nil or do nothing in this case
- messages have been added to errors and assertions to identify what and where the error was
- getmetatable() on an object will now return the object's class' prototype table, instead of the class table; it is still not possible to access the real metatables of any LibOOP-managed tables
Is there anything else that someone would like to see added or fixed?


Just now, I was also pondering the scheme for prototyping. In a way, it feels strange to me to just provide a special property on the class table for this purpose; it would be somehow "less obtrusive" to do it some other way, and leave the entire property space free for authors to do as they wish.

One thought was for :Class() and :Extend() to have a second return value which would be the prototype table. So, for example,
Code:
local MyClass,MyClass_proto = LibOOP:Class()
Then I'd probably want to also add a non-overridable method "GetPrototype" to both classes and objects to return the associated prototype table, but maybe this isn't necessary.

Comments?
taleden is offline   Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT. The time now is 07:44 PM.