Tuesday, April 14, 2009

The Joose Mutability branch

Nickolay has been hard at work pushing new features into Joose and refactoring the core to make it more accessible and extendible. Here is a feature of his work taken from the mailing list (be aware that this is core stuff. None of it should shine through to user level, although there are some nice new things comming out of it):

The sources are now divided into 3 "meta-levels".

Level 1 - Joose.Proto

This level uses the same inheritance principle, as ExtJS framework. Later I found out, that its
called "Crockford object model" (http://javascript.crockford.com/prototypal.html)
As an addition to this model, it possible to use this.SUPER() calls

Class definition at this level looks like:
TestClass = new Joose.Proto.Class('TestClass', {
isa : SuperClass,

attr1 : initValue1,
attr2 : initValue2,

method1 : function () { ... },
method2 : function () { ... }
}).c;
By default every class inherit from Joose.Proto.Object, which contains 'initialize', 'toString', 'SUPER', etc

At this level, attributes and methods are installed directly in class prototype, thus no Roles exists.

Level 2 - Joose.Managed

This level implements a set of classes, which represents "managed" properties.

Class definition at this level looks almost like Joose class:
TestClass = new Joose.Managed.Class('TestClass', {
isa : SuperClass,
does : [ Role ],

have : {
attr1 : initValue1,
attr2 : initValue2,
},

methods : {
method1 : function () { ... },
method2 : function () { ... }
},

after : {
method1 : function () { ... }
}
}).c;
At this level, attributes and methods are represented with instances of special classes. Also appears method modifiers.
Methods are installed not directly but with wrappers, which allow to use the same method in different classes - and implement Roles.

Also - there is no complex attributes on this level.

Level 3 - Joose.MetaClass, MetaRole

This level allows to use complex attributes and class definition looks like normal Joose class:
Class('TestClass', {
isa : SuperClass,
does : [ Role ]
...
});

Mutability

Any class or role, can be extended with following construction:
TestClass.meta.extend({
doesnt : [ Role ],
does : [ Role1],

havenot : [ attr1 ],

...
})
Attributes, methods, roles - can be removed or added. The change will reflect in whole inheritance hierarchy (for classes) or through all composition relations (for roles)

Inheritance

Mutability implied a switch toward single inheritance scheme. I think Roles fully compensate this.

Its safe to inherit from lower "meta-level" - calls to SUPER are transparent.
Inheritance is now cheap operation (due to "crockford model")

Roles


Roles are implemented on Traits spec (http://www.iam.unibe.ch/%7Escg/Archive/Papers/Scha03aTraits.pdf)
They support aliasing and excluding:
    Class('Creature', {
does : [{
role : Walk,
alias : {
stop : 'stopWalk'
},
exclude : [ 'stop' ]
}, {
role : Eat,
alias : {
stop : 'stopEat'
},
exclude : [ 'stop' ]
}]
});
Conflicting properties are marked with conflict markers.

Roles can be applied also to instances (with some limitations)


Class methods

Instead of classical class methods, built-in singletons (symbiont class) are implemented. They are defined via builder 'my' and can contain any usual class builders:
Class('TestClass', {
isa : SuperClass,
does : [ Role ]
...,


my : {
have : { attr1 : initValue1, attr2 : initValue2 },
methods : { method1 : function () { ... } },
does : [ Role ]
...
}
});
they can be accessed as:

if (TestClass.my.attr1 == 'foo') TestClass.my.method1();


Startup time

As inheritance operation is now cheap, startup time for now mostly depends from how many additional roles are applied to metaclasses.

Without additional attributes roles and "depended" roles its the same as for 2.0.
With all those features though, its about twice longer (all measurements were done in FF on Ubuntu)

This can be improved if roles will be applied grouped (not in sequential order), or if the roles will be included to the sources of appropriate classes from start.

SUPER calls (and serialization possibilty)

From start, SUPER calls on "managed" level were implemented with attached properties to wrappers.
Though this reflects on performance (each call to SUPER used up to 2 calls to arguments.callee.caller), so I switched them to use closures, like in current version. So now the SUPER calls are at the same performance, but seriazliation is not possible.

Though, this can be configurable, and if such performance hit is acceptable, serialization can be done (with some additional code)

Btw - about installing JS modules. Yes, Ingy already uses the approach when the JS modules are published on CPAN. Their's sources in his approach lies near the corresponding *.pm files.

And if we'll change the "target installation" directory to something different from standard perl library, we'll get a nice, separate JavaScript lilbrary. Thats the goal, I hope he'll do it.

Otherwise, I'm going to tweak the Module::Build::JSAN for this, it looks not complex.
Post a Comment