JavaScript Talents

Composable Units Of Reuse via Delegation Proxies

aka Roles, Mixins, Traits

Flight Mixins ... Anyone?


var Enumerable_first = (function trait () {
  return (function applicator () {
    this.first = function first () {
      return this[0];
    };
  });
}());

var Enumerable_last = (function trait () {
  function last () {
    return this[this.length - 1];
  }
  return (function applicator () {
    this.last = last;
  });
}());

Flight Mixins ... Anyone?


var bazList = ["foo", "bar", "baz"];
var dogList = ["the", "lazy", "dog"];

// - `first` always gets assigned its very own method.

Enumerable_first.call(bazList);
Enumerable_first.call(dogList);

// - `last` also reserves its own slot, but
//   will hold a reference into the closure.

Enumerable_last.call(bazList);  
Enumerable_last.call(dogList);

console.log(bazList.first === dogList.first); // `false`;
console.log(bazList.last === dogList.last);   // `true`;

How about a nicer syntax ...


trait Enumerable_first {
  applicator() {
    this.first = function () {
      return this[0];
    };
  }
}

trait Enumerable_last (
  function last () {
    return this[this.length - 1];
  }
  applicator() {
    this.last = last;
  }
}

... backed by an ES3 library?


var
  Enumerable_first = Trait.create(function (use, applicator) {
    applicator(function () {
      this.first = function () {
        return this[0];
      };
    });
  }),

  Enumerable_last = Trait.create(function (use, applicator) {
    function last () {
      return this[this.length - 1];
    }
    applicator(function () {
      this.last = last;
    });
  });
  • "Trait Composition Language"

    • Composable behavior ... "Traits from Traits"

    • Conflict resolution of equally named functionality

... hhmh, and where does the code-reuse come from?

Apply rules


trait CompositeTrait {        // trait declaration.

  use { Trait_A, Mixin_B, Applicable_C }

  //apply { Mixin_B }
    apply { Applicable_C, Trait_A }
}

var CompositeTrait = trait {  // trait expression.

  use { Mixin_B, Applicable_C }

    apply { Mixin_B:sanitize }
    apply { Applicable_C:rerender }
}
use {..} apply {..}


trait MinimumObservable {                 // trait declaration.

  use { Observable_SignalsAndSlots }

    apply all without { hasEventListener, removeEventListener }
}

var NoHasObservable = trait Observable {  // named trait expression.

  use { Observable_SignalsAndSlots }

    apply { Observable_SignalsAndSlots } without { hasEventListener }
}
use{..} apply all
use{..} apply all without {..}
use{..} apply {..} without {..}

Apply rules

trait Talktive_AB {

  use { Talktive_A, Talktive_B }

    apply { Talktive_A:smallTalk }
    apply { Talktive_B:smallTalk } as { talk }
    apply { Talktive_A:bigTalk } before { Talktive_B }
}

var talker = Talktive_AB.call({});

talker.talk();      // console does log       : " bbb "
talker.smallTalk(); // console does log       : " aaa "
talker.bigTalk();   // 1stly console does log : " AAA "
                    // 2ndly console does log : " BBB "
use{..} apply {..} as {..}

Apply rules

use{..} apply {..} after {..}
use{..} apply {..} before {..}

trait withCompositeView {
  use { withView_A, withView_B, withView_C }

    apply { withView_A:register }
      before { withView_B } before { withView_C }

    apply { withView_A:initialize }
      after { withView_B } after { withView_C }

    apply { withView_C:render }
      before { withView_B } after { withView_A }
}

Apply rules

var view = withCompositeView.call({});

view.register();      // behavior's execution order : A-B-C
view.initialize();    // behavior's execution order : C-B-A
view.render();        // behavior's execution order : A-C-B

All of the proposed trait composition functionality and its syntax gets proved by an already existing ES3 compatible library ...

Apply rules

var
  CompositeTrait = Trait.create(function (use, applicator) {

    use(Trait_A, Mixin_B, Applicable_C)

    //.apply(Mixin_B)
      .apply(Applicable_C, Trait_A);
  }),

  CompositeTrait = Trait.create(function (use, applicator) {

    use(Mixin_B, Applicable_C)

      .apply(Mixin_B, "sanitize")
      .apply(Applicable_C, "rerender");
  });
use(..).apply(..)

var
  MinimumObservable = Trait.create(function Observable () {

    use(Observable_SignalsAndSlots)

      .apply.all.without("hasEventListener", "removeEventListener");
  }),

  NoHasObservable = Trait.create(function Observable () {

    use(Observable_SignalsAndSlots)

      .apply(Observable_SignalsAndSlots).without("hasEventListener");
  });
use(..).apply.all
use(..).apply.all.without(..)
use(..).apply(..).without(..)

Apply rules

var Talktive_AB = Trait.create(function (use, applicator) {

  use(Talktive_A, Talktive_B)

    .apply(Talktive_A, "smallTalk")
    .apply(Talktive_B, "smallTalk").as("talk")
    .apply(Talktive_A, "bigTalk").before(Talktive_B);
});

var talker = Talktive_AB.call({});

talker.talk();      // console does log       : " bbb "
talker.smallTalk(); // console does log       : " aaa "
talker.bigTalk();   // 1stly console does log : " AAA "
                    // 2ndly console does log : " BBB "
use(..).apply(..).as(..)

Apply rules

use(..).apply(..).after(..)
use(..).apply(..).before(..)

... and some trait composition examples on JSFiddle.

... got it, but what is it with this   applicator     again? And how, in the end, do I compose objects/types?

applicator
trait withFirstLastEnumerable {
  function first() { return this[0]; }
  function last() { return this[this.length - 1]; }

  applicator() {
    this.first = first;
    this.last = last;
  }
}
trait withEnumerableListProxy {
  applicator(list) {
    withFirstLastEnumerable.call(list);

    this.first = function () { return list.first(); };
    this.last = function () { return list.last(); };
  }
}

applicator ‧ call / apply

local functions, trait composition and stateful traits

class EnumQueue {
  constructor() {
    var
      list  = [],
      queue = this;

    queue.enqueue = function(item) {
      return (list.push(item) && item);
    };
    queue.dequeue = function() {
      return (list.shift());
    };
    withEnumerableListProxy.call(queue, list);
  }
}

applicator ‧ call / apply

local functions, trait composition and stateful traits

Trait Manual / Cheat Sheet

Library Example

var
  withFirstLastEnumerable = Trait.create(function (use, applicator) {
    function first() { return this[0]; }
    function last() { return this[this.length - 1]; }

    applicator(function () {
      this.first = first;
      this.last = last;
    });
  }),
  withEnumerableListProxy = Trait.create(function (use, applicator) {
    applicator(function (list) {
      withFirstLastEnumerable.call(list);

      this.first = function () { return list.first(); };
      this.last = function () { return list.last(); };
    });
  });
applicator ‧ call / apply

local functions, trait composition and stateful traits

JavaScript Talents

Composable Units Of Reuse via Delegation Proxies

aka Roles, Mixins, Traits

Thank You.

JavaScript Talents - Composable Units of Reuse via Delegation Proxies

By Peter Seliger

JavaScript Talents - Composable Units of Reuse via Delegation Proxies

raw presentational Trait/Talent syntax sketch for 2017.jsunconf.eu. Each of the embedded JSFiddle examples runs in your browser. The embedded Jasmin test specification fails only at specs that rely on class syntax and fat arrow functions but do not get them provided by clients that run such tests. Have also a look on "Refactoring legacy mixin-based class hierarchies" at Stack Overflow ... http://stackoverflow.com/questions/43027388/refactoring-legacy-mixin-based-class-hierarchies

  • 2,057