Yay for Sugary JavaScript OO
So, JavaScript is an object oriented language and all that, we have discussed it previously to length, and I have quite expressed how confusing and rough all the primitives the language provides are when it comes to dealing with object composition and behaviour sharing — which is just no good for a prototypical language.
Well, the good thing is that we can provide our own abstractions for this overtly dynamic object orientation semantics through libraries, which is what I have been doing for a while. And now that Boo has gained some shape and use, it's time to release it officially.
Why? Oh god WHY?
So, what's this Boo
thing I'm talking about?
Well, my dearest friend, Boo
is my attempt at bringing some sugar and
sort-of declarative syntax for defining objects in JavaScript, which
makes the structuring of programs easier to follow and reason about.
So, instead of the ugly, factory-ish approach:
function Animal() { /* ... */ } function Cat() { /* ... */ } Cat.prototype = Object.create(Animal.prototype) Cat.prototype.say = function() { /* ... */ }
You can have a sort-of declarative one that works entirely around objects:
var Base = boo.Base var Animal = Base.derive({ /* Animal's properties here */ }) var Cat = Animal.derive({ /* Cat's properties here */ })
What Boo provides?
Boo is structured in three layers: mixins, inheritance and syntactical-sugar. Each of these serve to a particular purpose, and build upon the previous one — although you could say that the concepts themselves are orthogonal, which makes them composable.
Mixins
On the basic level, Boo
provides the developer with mixins and
data-objects — which are a specialised kind of mixins.
Mixins are parent-less objects that can be included in any object, at any time. Data-objects do the same thing, but they're also a factory for yielding the objects that will be included, which makes them a neat thing for default objects and such.
The primitives extend
and merge
account for all of these:
var ring_data = { items: [], max: 3, toData: function() { return { items: [], max: this.max }} } var ring = { push: function(item) { this.items.unshift(item) if (this.items.length > this.max) this.items.pop() } } var spells = boo.merge(ring, ring_data, { max: 2 }) spells.push('Watera') spells.push('Blizarra') spells.push('Firaga') spells.items // => ['Firaga', 'Blizarra'] ring_data.items // => [] boo.extend(spells, [ring_data]) spells.items // => [] spells.max // => 3
Inheritance
For inheritance, Boo
just defines a thin layer over the standard
Object.create
method, such that it accepts mixins rather than property
descriptors — which are way too verbose to be practical.
The primitive derive
is used for coupling inheritance and extension:
var Collection = { pop: function(){ return this.items.pop() }, push: function(item){ this.items.push(item) }, each: function(fn) { this.items.forEach(fn) }, map: function(fn) { return this.items = this.items.map(fn) } } // Clones the `collection' behaviours and extends the clone with the // `ring' behaviours var RingCollection = boo.derive(Collection, ring) var spells = boo.derive(RingCollection, ring_data) RingCollection.isPrototypeOf(spells) // => true spells.push('Agi') spells.push('Dia') spells.push('Patra') spells.push('Recarm') spells.map(function(spell){ return spell.toUpperCase() }) // => ['RECARM', 'PATRA', 'DIA']
Syntactical sugar
Last but not least, Boo
gives the developer nice object-oriented
syntactical sugar to write all of this in a nice sort-of declarative
syntax, which is easier to read and reason about.
The base object is Base
, which can be cloned through derive
and
instantiated with make
, although both use cloning in the prototypical
sense:
var Enum = boo.Base.derive({ each: function(fn) { this.items.forEach(fn) }, filter: function(fn) { return this.items = this.items.filter(fn) }, map: function(fn) { return this.items = this.items.map(fn) }, fold: function(fn, start) { return this.items.reduce(fn, start) } }) var Coll = Enum.derive({ push: function(item) { this.items.push(item) }, pop: function(item) { this.items.pop(item) }, has_p: function(item) { return this.find(item) != -1 }, find: function(item) { return this.items.indexOf(item) } }) var my_coll = Coll.make() my_coll.items = [1, 2, 3] my_coll.map(function(n){ return n * n }) // => [1, 4, 9] my_coll.find(4) // => 1
What's next?
Boo
's source code is all on Github:
$ git clone http://github.com/robotlolita/boo.git
Though, if you want a quick'n'dirty install, just get it from NPM:
$ npm install boo
node> var boo = require('boo')
node> var Stuff = boo.Base.derive({ ... })
Future developments?
Next release of Boo
will include traits, and should be up around
December. Traits are more interesting for structuring larger programs
than mixins are, but I have yet to experiment with them more until I can
stick with a nice API :3
Acknowledgements
Thanks for AdamR
in the comments and xivix
on Freenode's
##javascript
channel for pointing out the issue with Object.create on
the first snippet.