arbitrait

0.1.1 • Public • Published

What and Why

Loosely based on traits in Rust, traits in JavaScript/LiveScript provide a way to safely extend objects without modifying them.

Everyone knows we shouldn't modify Array.prototype. But what if we really want to have, say, array.first()? We can get something close, compartmentalised neatly and harmlessly in a trait!

npm install arbitrait
const trait = require("arbitrait");

// define a trait
const Serial = trait({
    slice: function(s, e){
        return Array.prototype.slice.call(this, s, e);
    },
    // where no default implementation can be written, specify the number of arguments a method should take
    first: 1, // first(cb), returns first element of a collection that satisfies a callback
    last: 1, // last(cb), returns last element that satisfies a callback
});

// implement our trait for Array and HTMLCollection
Serial.implement([Array, HTMLCollection], {
    first: function(cb){
        for(let i = 0; i < this.length; i++) if(cb(this[i])) return this[i];
    },
    last: function(cb){
        for(let i = this.length - 1; i >= 0; i--) if(cb(this[i])) return this[i];
    }
    // we could override slice for these but the default is fine
});

Then to use it we need to wrap our Array/HTMLCollection with the trait to provide the context:

console.log(
    Serial(document.anchors)
        .first(a => /^https/.test(a.href))
        .href
);

That is, call first() on document.anchors, in the context of it being ('as') a Serial.

See the example/ directory for a more practical use of traits.

Common interface, tailored implementations

So we have some constructors...

const Person = function (name, occupation) { this.name = name; this.occupation = occupation };
const Dog = function (name) { this.name = name; };
const Sheep = function (name, fluffinessFactor) { this.name = name; this.fluffinessFactor = fluffinessFactor };

Let's make a trait

const Talk = trait({
    happy: 0,
    sad: 0
});

And implement it for our constructors

Talk.implement(Person, {
    happy: function () { return "Hooray! I love being a " + this.occupation; },
    sad: function () { return "Oh no!"; }
});

Talk.implement(Dog, {
    happy: function () { return "Woof!"; },
    sad: function () { return "*whiiiine*"; }
});

Talk.implement(Sheep, {
    happy: function () { return "Mehhh."; },
    sad: function () { return "BEHHH!"; }
});

Now let's make some objects

var dude = new Person("Billy", "programmer");
var mutt = new Dog("Fido");
var ewe = new Sheep("Dolly", 100000);

And make them speak!

console.log("Person, happy:", Talk(dude).happy());
console.log("Person, sad:", Talk(dude).sad());
console.log("Dog, happy:", Talk(mutt).happy());
console.log("Sheep, sad:", Talk(ewe).sad());

/*
Person, happy: Hooray! I love being a programmer
Person, sad: Oh no!
Dog, happy: Woof!
Sheep, sad: BEHHH!
*/

Notes

  • I'm pondering whether we should walk prototype chains and/or leverage instanceof to effectively inherit trait implementations. For now you can explicitly copy/inherit a trait implemenation with MyTrait.implement(ForConstructor, FromConstructor).
  • Check if a value has a trait implemented with MyTrait.implementedFor(myValue.constructor).

Readme

Keywords

Package Sidebar

Install

npm i arbitrait

Weekly Downloads

1

Version

0.1.1

License

MIT

Unpacked Size

7.92 kB

Total Files

4

Last publish

Collaborators

  • thecynicslantern