Blog Archive

2014-06-05

Die-vielen-Talente-von-JavaScript

Die vielen »Talente« von JavaScript

Die vielen Talente von JavaScript Rollen-orientierte Programmieransätze wie Traits und Mixins verallgemeinern zu können

TL;DR / Abriss

  • Rollen-orientiertes Programmieren ist ein strukturelles Programmierparadigma aus der Objekt-orientierten Programmierung, welches hilft, unterschiedliche Anforderungen voneinander zu Trennen (Separation of Concerns).
  • »Mixin« und »Trait« sind grundlegende Merkmale einiger Programmiersprachen und haben sich als Konzept und Bezeichnung etabliert; beide genügen dem Rollen-Paradigma.
  • Mixin- und Trait-basierte Komposition findet überwiegend auf Klassenebene statt.
  • Traits stellen Operatoren zur Verfügung, um Konflikte während der Komposition aufzulösen; d.h. die Reihenfolge unterschiedlicher Kompositionsschritte ist nicht so zwingend vorgegeben wie bei Mixins, welche ausschließlich lineare Komposition unterstützen.
  • Verhalten, welches durch Mixins und Traits zur Verfügung gestellt wird, kann zur Laufzeit weder hinzugefügt noch weggenommen werden.
  • Gerade für Traits war lange unklar, wie diese mit Zustand umzugehen haben.
  • Talente sind ein akademisches Konzept, welches Rollen-Ansätze wie Traits und Mixins weiterdenkt, indem es einige ihrer Fähigkeiten weiterentwickelt und ein paar ihrer Merkmale zusammenführt und damit deren Einsatz in der Praxis vereinfacht/erleichtert.
  • Aufgrund der Sprachkernkonzepte wie »Closure« und »Delegation« sind JavaScript und Talente füreinander bestimmt.

Delegationsprinzipien

JavaScript1 ist eine Delegationssprache mit sowohl selbstausführendem als auch direktem Delegationsmechanismus.

Funktionsobjekte als Rollen (Traits und Mixins)

JavaScript unterstützt2 3 4 5 schon auf der Ebene des Sprachkerns verschiedene auf Funktionsobjekten aufbauende Implementierungen des Rollen-Musters wie z.B. Traits und Mixins. Zusätzliches Verhalten wird bereitgestellt, indem mindestens eine Methode über das Schlüsselwort this6 im Rumpf eines function7-Objekts gebunden wird. Benötigt ein Objekt zusätzliches Verhalten, welches ihm nicht über die Prototypenkette8 zur Verfügung gestellt werden kann, lässt sich eine Rolle direkt über call9 bzw. apply10 an dieses Objekt delegieren.

var Enumerable_first = function () {
  this.first = function () {
    return this[0];
  };
};
var list = ["foo", "bar", "baz"];

console.log("(typeof list.first)", (typeof list.first)); // "undefined"

Enumerable_first.call(list); // explicit delegation

console.log("list.first()", list.first()); // "foo"

Objektkomposition und Vererbung durch Delegation

Während Komposition in JavaScript über diese direkte Delegation abgedeckt werden kann, kommt automatische Delegation immer dann zur Anwendung, wenn der Interpreter die Prototypenkette eines Objekts nach oben hin abwandern muss, um z.B. eine mit diesem Objekt assoziierte Methode zu finden, die diesem nicht unmittelbar gehört. Sobald die Methode gefunden ist, wird sie im Kontext dieses Objekts aufgerufen. Demzufolge wird Vererbung in JavaScript über einen selbstausführenden Delegationsmechanismus abgebildet, der an die prototype-Eigenschaft von Konstruktorfunktionen gebunden ist.

var Enumerable_first_last = function () {
  this.first = function () {
    return this[0];
  };
  this.last = function () {
    return this[this.length - 1];
  };
};
console.log("(typeof list.first)", (typeof list.first));  // "function"   // as expected
console.log("(typeof list.last)", (typeof list.last));    // "undefined"  // of course

Enumerable_first_last.call(Array.prototype);  // applying behavior to [Array.prototype]

console.log("list.last()", list.last());      // "baz"  // due to delegation automatism

Von den Schwierigkeiten die durch Rollen etablierten Konzepte und Begrifflichkeiten auf JavaScript anzuwenden

»Mixins«

Folgt man der deutschsprachigen Wikipedia-Version, war Flavors, …

… die erste objektorientierte Erweiterung in der Programmiersprachenfamilie Lisp
, (die) … erstmals eine Mehrfachvererbung in der objektorientierten Programmierung unterstützt.
(Wobei) … auch Mixins, ein spezielles Entwurfsmuster im Zusammenhang mit der Mehrfachvererbung, erstmals unterstützt (wurden).

Mit diesem Konzept wurde es möglich, Quellcode auf Klassenebene wiederzuverwenden. Obwohl Mixin-Ansätze über die Jahre hinweg weiterentwickelt wurden, ließe sich sehr kurz zusammengefasst sagen, dass sich Komposition auch weiterhin auf Klassen beschränkt(, obwohl z.B. Ruby diese auch auf Instanzebene unterstützt). Und wegen des Fehlens geeigneter Kompositionsoperatoren, welche dabei helfen, Namenskonflikte zwischen miteinander konkurrierenden Methoden zu lösen, erfolgt Mixin-Komposition immer geradlinig in geordeter Reihenfolge.

»Traits«

Das Konzept der Traits11 wurde eine Dekade lang wissenschaftlich besonders intensiv erforscht und weiterentwickelt. Erste Veröffentlichungen der »Software Composition Group«12 (SCG) an der Universität Bern reichen zurück bis November 200213.

Traits überwinden einige Schranken Mixin-gestützter Komposition. Traits stellen für letzteres Operatoren für Kombination, Ausschluß und Umbennung/Parallelbezeichnung von Methoden zur Verfügung. Durch diese Flexibilität erschließen sie sich im Vergleich zu Mixins mehr Anwendungsfälle. Und da zustandsbehaftete Traits14 erstmals 2006 in einem weiteren Entwicklungsschritt beschrieben wurden (die ersten SCG Traits hatten zustandlos zu sein), sah es zumindest in den letzten Jahren ganz so aus, als ob die besonderen Sprachmerkmale von JavaScript und eine rein auf Funktionen basierende Umsetzung des Traits-Konzepts füreinander bestimmt seien.

JavaScript als klassenlose, fast komplett auf Objekten basierende, auf vielfältige Weise hochdynamische Delegationssprache mit großem funktionalen Naturell ist aber mühelos in der Lage, weit über die Grenzen der Traits hinauszugehen. Deren Konzept unterstützt z.B. nicht Verhaltensveränderungen von Objekten zur Laufzeit. Und obwohl Trait-Komposition sehr flexibel ist, beschränkt sie sich auf die Ebene von Klassen und anderer Traits. Außerdem lassen sich Traits zur Programmlaufzeit weder zu Klassen/Traits hinzufügen noch von diesen entfernen.

Seit geraumer Zeit aber gibt es einen heimlichen Neuzugang15, welcher JavaScript auf Augenhöhe begegnet. Somit liegt der Ball für das spielerische Ausloten der neuen Möglichkeiten und zum “an sich selber wachsen” wiederum bei JavaScript, wobei diese Entdeckungsreise eben erst beginnt …

»Talents: Dynamically Composable Units of Reuse« + + + »Talente: dynamisch komponier- und wiederverwendbare Einheiten«

Die gleichnamige SCG-Veröffentlichung vom Juli 2011 beschreibt Talente16 als …

… object-specific units of reuse which model features that an object can acquire at run-time. Like a trait, a talent represents a set of methods that constitute part of the behavior of an object. Unlike traits, talents can be acquired (or lost) dynamically. When a talent is applied to an object, no other instance of the object’s class are affected. Talents may be composed of other talents, however, as with traits, the composition order is irrelevant. Conflicts must be explicitly resolved.

Like traits, talents can be flattened, either by incorporating the talent into an existing class, or by introducing a new class with the new methods. However, flattening is purely static and results in the loss of the dynamic description of the talent on the object. Flattening is not mandatory, on the contrary, it is just a convenience feature which shows how traits are a subset of talents.

Und das wiederum hört sich nach einem perfekten Treffer an; als ob das Konzept der Talente ganau auf das Sprachdesign von JavaScript abgestimmt wurde, auf dass Talente auf dem für diese Sprache natürlichstem Wege umsetzbar sind. Komposition erfolgt sowohl auf reiner Objekt-/Instanz-Ebene als auch auf Konstruktor-/”Klassen”-Ebene. Und … Talente berücksichtigen Zustand.

Vom »Mixin« zum »Trait« - die Wirkungsweise rein Funktions-getriebener »Talente« in JavaScript

Den wissenschaftlichen Erkenntnissen zufolge sollen Talente sowohl auf Instanz- als auch auf Klassen-Ebenen wirken. Dabei können Talente so einfach und zielgerichtet wie Mixins eingesetzt werden; über sie darf aber auch zusätzliche Funktionlität für Kompositions-Operationen zur Verfügung stehen, damit sie wie Traits benutzt werden können.

Der »Mixin-Ansatz«

Das strukturell einfachste Talent setzt auf zustandlosem Weg genau eine Methode um und berücksichtigt möglicherweise autretende Konflikte nicht. Für JavaScript bietet sich die Umsetzung der schon einmal weiter oben verwendeten Verhaltens-Funktionalität für abzählbare Listen an - das Beispiel von Enumerable_first sieht dann in einem ersten Schritt so aus:

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

Die Anwendung dieses Talents auf ein Array bringt letzterem das im Beispielcode festgelegte Verhalten bei …

var list = ["foo", "bar", "baz"];

Enumerable_first.call(list);

console.log("list.first()", list.first()); // "foo"

.., das Agieren auf “Klassen”-Ebene (Array.prototype) ohne Konfliktlösung, kommt dem klassischen Mixin-Ansatz dann so nahe wie möglich …

var list = ["foo", "bar", "baz"];

Enumerable_first.call(Array.prototype);

console.log("list.first()", list.first()); // "foo"

Der Einsatz dieses Musters, sowohl auf “Klassen”- als auch auf “Instanz”- Ebene wurde 2011 von Angus Croll wiederentdeckt und von ihm mit dem Namen Flight Mixin bedacht.

Obwohl dieser erste Ansatz funktioniert, weist er ein paar Nachteile auf, die es zu korrigieren gilt. Denn momentan wird für jedes Objekt, auf welches das Talent angewendet wird, eine eigene unreferenzierte first-Methode erzeugt.

delete Array.prototype.first;

var
  list = ["foo", "bar", "baz"],
  arr = ["biz", "buzz"]
;
Enumerable_first.call(list);
Enumerable_first.call(arr);

console.log("arr.first()", arr.first()); // "biz"
console.log("(list.first === arr.first)", (list.first === arr.first)); // false

Für eine einzelne, speicherschonend referenzierbare Methode muss das schon umgesetzte Talent in ein »Module Pattern« verpackt werden - zweiter Schritt:

var Enumerable_first = (function () {

  var
    Mixin,

    first = function () {
      return this[0];
    }
  ;
  Mixin = function () {
    this.first = first;
  };

  return Mixin;

}());

var
  list = ["foo", "bar", "baz"],
  arr = ["biz", "buzz"]
;
Enumerable_first.call(list);
Enumerable_first.call(arr);

console.log("list.first()", list.first());  // "foo"
console.log("arr.first()", arr.first());    // "biz"

console.log("(list.first === arr.first)", (list.first === arr.first)); // true

Da sich der letzte Ansatz als tragfähig erwiesen hat, kann er in einem dritten Schritt wieder gekürzt werden zu …

var Enumerable_first = (function () { // Pure Talent / Lean Mixin.
  var first = function () {
    return this[0];
  };
  return function () { // Mixin mechanics refering to shared code.
    this.first = first;
  };
}());

»Pure Talent« und »Lean Mixin«

An diesem Punkt ließe sich folgendes formulieren …

Jede Umsetzung eines Rollen-Musters soll als Talent bezeichnet werden.

… und …

Jede auf dem Module Pattern basierende zustandslose Umsetzung einer Rolle, die Referenzierung unterstützt und ohne Konfliktlösung auskommt, soll als Pure Talent und/oder Lean Mixin bezeichnet werden.

Spezialisierte »Mixins« als Fundamente für »Traits«

Im folgerichtig nächsten Schritt wäre es denkbar, ein Resolvable Lean Mixin zu entwickeln, welches Kompsoitionsmethoden wie resolveBefore, resolveAfter, resolveAround und resolveWithAlias bereitstellt, um die durch Traits zu unterstützende Fähigkeit zur Konfliktlösung umzusetzen.

Um die nachfolgenden Beispiele lauffähig zu halten, sind diese auf zusätzlich unterstützenden Quellcode angewiesen, welcher die Grundlagen zur Auflösung von Konflikten gleichnamiger konkurrierender Methoden liefert.

Der Quellcode eines Resolvable-Ansatzes, aufbauend auf dem Lean Mixin-Muster, würde dann höchstwahrscheinlich aussehen wie im nächsten Beispiel:

var Resolvable = (function () { // Pure Talent / Lean Mixin.

  var
    Mixin,

    isString    = function (type) {
      return (typeof type == "string");
    },
    isFunction  = function (type) {
      return (typeof type == "function");
    },

    resolveBefore = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].before(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAfter = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].after(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAround = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].around(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },

    resolveWithAlias = function (methodName, aliasName, rivalingMethod) {
      if (
        isString(methodName)
        && isString(aliasName)

        && (methodName != aliasName)

        && isFunction(rivalingMethod)
      ) {
        this[aliasName] = rivalingMethod;
      }
    }
  ;

  Mixin = function () { // Mixin mechanics refering to shared code.
    var type = this;

    type.resolveBefore    = resolveBefore;
    type.resolveAfter     = resolveAfter;
    type.resolveAround    = resolveAround;
    type.resolveWithAlias = resolveWithAlias;
  };

  return Mixin;

}());

Ein Anwendungsfall des gerade erstellten Resolvable-Talents/Mixins könnte dann so aussehen:

var Floatable = function () { // implementing a [Floatable] Trait.

  this.resolveAfter("initialize", function () {
    console.log("make it floatable.");
  });
};
var Ridable = function () { // implementing a [Ridable] Trait.

  this.resolveBefore("initialize", function () {
    console.log("make it ridable.");
  });
};

var Amphicar = function () {  // implementing an [Amphicar] Constructor.

  Resolvable.call(this);      // applying the [Resolvable] Mixin.

  Floatable.call(this);       // applying the [Floatable] Trait.
  Ridable.call(this);         // applying the [Ridable] Trait.

  this.initialize();
};

var ac = new Amphicar;
// "make it ridable."
// "make it floatable."

console.log(Object.keys(ac));
// ["resolveBefore", "resolveAfter", "resolveAround", "resolveWithAlias", "initialize"]

Wie sich unschwer erkennen lässt, erzwingt diese einfache Umsetzung eines Resolvable-Talents für jede Amphicar-Instanz 4 adressierbare Methoden, die aber wirklich nicht sichtbar sein sollten.

Dennoch, der ausschließlich auf Funktionen und Delegation bauende Ansatz ist tragfähig genug, ein Resolvable voll funktionsfähig umzusetzen und dessen Wirkungsweise vor der Außenwelt zu verstecken.

Ein Hidden/Undercover Talent oder Silent/Stealth Mixin verändert das Objekt, auf welches es direkt wirkt, nicht. Sein Hauptziel ist es, den Bezugspunkt seines Wirkens (context) zu sichern, um diesen Kontext dann an zusätzliches Verhalten zu delegieren, welches nach außen hin niemals sichtbar wird. Deswegen erweitert ein solches Mixin ein zusätzlich injiziertes (lokales) Proxy-Objekt um genau diese Delegationsfunktionalität.

Der »Stealth Mixin«-Ansatz, der hier Resolvable_silent genannten Variante, versteckt seine Konfliktlösungsmethoden vor jeder Amphicar-Instanz, wie es das nächste Beispiel sofort anschaulich demonstrieren wird:

var Resolvable_silent = (function () { // Undercover Talent / Stealth Mixin.

  var
    StealthMixin,

    isString    = function (type) {
      return (typeof type == "string");
    },
    isFunction  = function (type) {
      return (typeof type == "function");
    },

    resolveBefore = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].before(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAfter = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].after(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAround = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].around(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },

    resolveWithAlias = function (methodName, aliasName, rivalingMethod) {
      if (
        isString(methodName)
        && isString(aliasName)

        && (methodName != aliasName)

        && isFunction(rivalingMethod)
      ) {
        this[aliasName] = rivalingMethod;
      }
    }
  ;

  StealthMixin = function (resolvableProxy) { // Stealth Mixin using an injected proxy.
    if (resolvableProxy && (typeof resolvableProxy == "object")) {

      var type = this;
  /*
   *  special functional Mixin pattern that preserves and foldes
   *  two different contexts and does delegation with partly code reuse.
   */
      resolvableProxy.resolveBefore    = function (/*methodName, rivalingMethod*/) {
        resolveBefore.apply(type, arguments);
      };
      resolvableProxy.resolveAfter     = function (/*methodName, rivalingMethod*/) {
        resolveAfter.apply(type, arguments);
      };
      resolvableProxy.resolveAround    = function (/*methodName, rivalingMethod*/) {
        resolveAround.apply(type, arguments);
      };
      resolvableProxy.resolveWithAlias = function (/*methodName, aliasName, rivalingMethod*/) {
        resolveWithAlias.apply(type, arguments);
      };
    }
  };

  return StealthMixin;

}());

Der Quellcode des weiter oben schon einmal benutzten Anwendungsfalls für Resolvable lässt sich dann leicht abändern zu …

var Floatable = function () { // implementing a "silent" [Floatable] Trait featuring ...
                              // ... the "Stealth Mixin" variant of [Resolvable].
  var resolvable = {};
  Resolvable_silent.call(this, resolvable);

  resolvable.resolveAfter("initialize", function () {
    console.log("initialize :: make this floatable.", this);
  });
};
var Ridable = function () { // implementing a "silent" [Ridable] Trait featuring ...
                            // ... the "Stealth Mixin" variant of [Resolvable].
  var resolvable = {};
  Resolvable_silent.call(this, resolvable);

  resolvable.resolveBefore("initialize", function () {
    console.log("initialize :: make this ridable.", this);
  });
};

var Amphicar = function () {  // implementing the [Amphicar] Constructor.

  Floatable.call(this);       // applying the "silent" [Floatable] Trait.
  Ridable.call(this);         // applying the "silent" [Ridable] Trait.

  this.initialize();
};

var ac = new Amphicar;
// initialize :: make this ridable.   Amphicar {initialize: function}
// initialize :: make this floatable. Amphicar {initialize: function}

console.log("the new amphicar", ac);
// the new amphicar                   Amphicar {initialize: function}

.., und stellt damit anschaulich die Vorzüge des auf Funktionen basierenden Konzepts für Stealth Mixins und Silent Traits unter Beweis.

»Undercover Talent«, »Stealth Mixin« und »Silent Trait«

Jede Umsetzung einer Rolle, die ihren this-Kontext unverändert lässt, dafür aber einen zusätzlich injizierten Proxy um Verhalten erweitert, welches wiederum Kontext und Proxy durch Delegation miteinander verschränkt, um dieses Verhalten nicht öffentlich abzubilden, soll als Hidden/Undercover Talent und/oder Silent/Stealth Mixin bezeichnet werden.

.

Es existieren Lösungen zum Konfliktlösungsverhalten, die auf Basis von Undercover Talent- / Stealth Mixin- Mustern umgesetzt wurden, und die als Resolvable bezeichnet werden.

.

Jede Umsetzung einer Rolle, die eine Zusammensetzung aus mindestens einem Talent und einem Stealth Mixin-basierten Resolvable ist, soll als Silent Trait bezeichnet werden.

Die derzeitige Systematik funktionaler »Talente«

Die Betrachtung von Zustand, dessen Existenz sowie seine Herkunft, und wie Zustand dann nur erweitert oder vollkommen verändert wird, bestimmt inhaltlich den letzten Teil dieses Dokuments. Diese Richtschnur hilft dabei, noch tiefer in die spezielle Materie der Talente einzutauchen. Zum besseren Verständnis werden ausschließlich Beispiele aus der Praxis herangezogen.

»Taxonomy Matrix« einiger möglicher Talente-Muster

Meine früheren Dokumente und Presentationen zeigen eine Art »Trait Taxonomy Matrix« der vielen Trait / Mixin Varianten als direktes Resultat meines Versuchs, die von mir in der Praxis meistgenutzten Implementierungen des Rollen-Musters zu klassifizieren.

Da nun aber Ausdrücke wie »Trait« und »Mixin« das mit JavaScript mögliche Rollen-Spektrum nicht vollständig abdecken, wurden »Talente« als Konzept und Begriff zur neuen Arbeitsgrundlage, um alle schon in der Praxis identifizierten Varianten an Rollen-basiertem »Code-reuse« abzudecken. Anhand der folgenden »State«-Koordinaten wurde die zuletzt gültige Begrifflichkeit komplett geändert zu …

state stateless no active mutation actively mutating proxy augmentation
stateless Pure Talent
Lean Mixin
- - -
created - Smart Talent
(Skilled Talent)
Smart Talent
(Skillful Talent)
-
injected - Smart Talent
(Promoted Talent)
Smart Talent
(Privileged Talent)
-
both created and injected - Smart Talent
(Privileged Talent)
Smart Talent
(Privileged Talent)
-
injected proxy - - - Hidden/Undercover Talent
Silent/Stealth Mixin

Die hier abgebildete »Taxonomy Matrix« ist nicht in Stein gemeißelt. Es handelt sich vielmehr um eine Momentaufnahme, die auf Bewertung wartet, gerne auch auf kritische. Jeder der denkt bzw. sich dazu berufen fühlt, etwas beitragen zu können bzw. zu müssen, ist hiermit aufgefordert, dies zu tun.

Ausgewählte »Smart Talent«-Muster

Ein Pure Talent zeichnet sich durch absolute Zustandslosigkeit aus, und das Hidden/Undercover Talent ist hinsichtlich seines Proxy-Objekts streng definiert. Im Kontrast dazu muss jede Smart Talent-Variante Zustand besitzen, egal ob innerhalb des Talents erzeugt oder in ebendieses injiziert, egal ob aktiv veränderbar oder eben unveränderlich.

Jede umgesetzte Variante einer Rolle, die zustandsbehaftet ist, egal ob Zustand von außen heineingetragen oder dieser intern selbst definiert wurde und dessen Veränderlichkeit außer Acht lassend, soll als Smart Talent bezeichnet werden.

Es gibt bestimmt mehr Smart Talent-Varianten als die 6 in der Matrix aufgeführten. Und es bedarf dringendem Nachdenkens und guter Diskussion, wie sinnvoll es sowohl aus theoretischer als auch aus praktischer Sicht ist, alle möglichen Varianten unterscheiden und benennen zu wollen. Es gibt aber für mindestens 2 von ihnen ausgewiesene Anwendungsfälle aus der Praxis.

Jede Umsetzung einer Rolle, die auf zusätzlich injizierten Zustand angewiesen ist, diesen aber nur liest und damit unverändert lässt, soll als Promoted Talent bezeichnet werden.

Anwendungsfälle wären dann zum einen ein Wrapper für selbstdefinierte abzählbare Listen, Enumerable_first_last_item_listWrapper

var Enumerable_first_last_item_listWrapper = (function () {

  var
    global = this,

    Talent,

    parse_float = global.parseFloat,
    math_floor  = global.Math.floor
  ;
  Talent = function (list) { // implementing the "promoted" [Enumerable] Talent.
    var
      enumerable = this
    ;
    enumerable.first = function () {
      return list[0];
    };
    enumerable.last = function () {
      return list[list.length - 1];
    };
    enumerable.item = function (idx) {
      return list[math_floor(parse_float(idx, 10))];
    };
  };

  return Talent;

}).call(null);

… und Allocable zum anderen, ebenfalls ein Wrapper, der den kontrollierten Zugriff von außen auf die eingeschlossene Listenstruktur umsetzt und sich dabei auch des ersten Wrappers bedient …

var Allocable = (function () {

  var
    global = this,

    Enumerable_listWrapper = global.Enumerable_first_last_item_listWrapper,

    Talent,

    Array = global.Array,

    array_from = ((typeof Array.from == "function") && Array.from) || (function (proto_slice) {
      return function (listType) {

        return proto_slice.call(listType);
      };
    }(Array.prototype.slice))
  ;

  Talent = function (list) { // implementing the "promoted" [Allocable] Talent.
    var
      allocable = this
    ;
    allocable.valueOf = allocable.toArray = function () {
      return array_from(list);
    };
    allocable.toString = function () {
      return ("" + list);
    };
    allocable.size = function () {
      return list.length;
    };
    Enumerable_listWrapper.call(allocable, list);
  };

  return Talent;

}).call(this);

.., womit beide Wrapper schon die Grundlage für selbstdefinierte Collections, wie z.B. Queue, liefern:

var Queue = (function () {

  var
    global = this,
    Allocable = global.Allocable
  ;

  return function () { // implementing the [Queue] Constructor.
    var list = [];

    this.enqueue = function (type) {

      list.push(type);
      return type;
    };
    this.dequeue = function () {

      return list.shift();
    };

    Allocable.call(this, list); // applying the "promoted" [Allocable] Talent.
  };

}).call(null);



var q = new Queue;

console.log("q.size()", q.size());        // 0

q.enqueue("the");
q.enqueue("quick");
q.enqueue("brown");
q.enqueue("fox");

console.log("q.size()", q.size());        // 4
console.log("q.toArray()", q.toArray());  // ["the", "quick", "brown", "fox"]

console.log("q.first()", q.first());      // "the"
console.log("q.item(1)", q.item(1));      // "quick"
console.log("q.last()", q.last());        // "fox"

q.dequeue();
q.dequeue();

console.log("q.toArray()", q.toArray());  // ["brown", "fox"]
console.log("q.size()", q.size());        // 2

q.dequeue();
q.dequeue();
q.dequeue();

console.log("q.size()", q.size());        // 0

»Skilled Talent« und »Skillful Talent«

Jede Umsetzung einer Rolle, die veränderlichen Zustand selbst erzeugt, um ihre Aufgabe(n) erfüllen zu können und dabei zu keinem Zeitpunkt auf zusätzlich injizierten Zustand angewiesen ist, soll als Skilled Talent oder Skillful Talent bezeichnet werden.

Observable ist wahrscheinlich das am häufigsten anzutreffende praxisbezogene Beispiel für Mixins. Der folgende Quellcode entspricht in seiner Umsetzung dem Skillful Talent, denn bei seiner Anwendung werden viele verschiedene Zustände erzeugt, gelesen und verändert:

var Observable_SignalsAndSlots = (function () { // implementing a "skillful" [Observable] Talent.

  var
    global = this,
    Array = global.Array,

    isFunction = function (type) {
      return (
        (typeof type == "function")
        && (typeof type.call == "function")
        && (typeof type.apply == "function")
      );
    },
    isString = (function (regXCLassName, expose_implementation) {
      return function (type) {

        return regXCLassName.test(expose_implementation.call(type));
      };
    }((/^\[object\s+String\]$/), global.Object.prototype.toString)),

    array_from = ((typeof Array.from == "function") && Array.from) || (function (proto_slice) {
      return function (listType) {

        return proto_slice.call(listType);
      };
    }(Array.prototype.slice))
  ;

  var
    Event = function (target, type) {

      this.target = target;
      this.type = type;
    },

    EventListener = function (target, type, handler) {

      var defaultEvent = new Event(target, type);             // creation of new state.

      this.handleEvent = function (evt) {
        if (evt && (typeof evt == "object")) {

          evt.target = defaultEvent.target;
          evt.type = defaultEvent.type;

        } else {

          evt = {
            target: defaultEvent.target,
            type: defaultEvent.type
          };
        }
        handler(evt);
      };
      this.getType = function () {
        return type;
      };
      this.getHandler = function () {
        return handler;
      };
    },

    EventTargetMixin = function () { // implementing the [EventTarget] Mixin as "skillful" Talent.

      var eventMap = {};                                      // mutable state.

      this.addEventListener = function (type, handler) {      // will trigger creation of new state.
        var reference;
        if (type && isString(type) && isFunction(handler)) {
          var
            event = eventMap[type],
            listener = new EventListener(this, type, handler) // creation of new state.
          ;
          if (event) {
            var
              handlers = event.handlers,
              listeners = event.listeners,
              idx = handlers.indexOf(handler)
              ;
            if (idx == -1) {
              handlers.push(listener.getHandler());
              listeners.push(listener);

              reference = listener;
            } else {
              reference = listeners[idx];
            }
          } else {
            event = eventMap[type] = {};
            event.handlers = [listener.getHandler()];
            event.listeners = [listener];

            reference = listener;
          }
        }
        return reference;
      };

      this.dispatchEvent = function (evt) {
        var
          successfully = false,
          type = (
            (evt && (typeof evt == "object") && isString(evt.type) && evt.type)
            || (isString(evt) && evt)
          ),
          event = (type && eventMap[type])
        ;
        if (event) {
          var
            listeners = (event.listeners && array_from(event.listeners)),
            len = ((listeners && listeners.length) || 0),
            idx = -1
          ;
          if (len >= 1) {
            while (++idx < len) {

              listeners[idx].handleEvent(evt);
            }
            successfully = true;
          }
        }
        return successfully;
      };
    }
  ;

  return EventTargetMixin; // will be exposed to the outside as [Observable_SignalsAndSlots].

}).call(null);

Der Quellcode, der schon einmal weiter oben gezeigten beispielhaften Umsetzung einer Queue, macht sich im nächsten Schritt Observable_SignalsAndSlots zunutze und könnte dann in folgende Richtung verbessert werden:

var Queue = (function () {

  var
    global = this,

    Observable  = global.Observable_SignalsAndSlots,
    Allocable   = global.Allocable,

    Queue,

    onEnqueue = function (queue, type) {
      queue.dispatchEvent({type: "enqueue", item: type});
    },
    onDequeue = function (queue, type) {
      queue.dispatchEvent({type: "dequeue", item: type});
    },
    onEmpty = function (queue) {
      queue.dispatchEvent("empty");
    }
  ;

  Queue = function () { // implementing the [Queue] Constructor.
    var
      queue = this,
      list = []
    ;
    queue.enqueue = function (type) {

      list.push(type);
      onEnqueue(queue, type);

      return type;
    };
    queue.dequeue = function () {

      var type = list.shift();
      onDequeue(queue, type);

      (list.length || onEmpty(queue));

      return type;
    };
    Observable.call(queue);       // applying the "skillful" [Observable_SignalsAndSlots] Talent.
    Allocable.call(queue, list);  // applying the "promoted" [Allocable] Talent.
  };

  return Queue;

}).call(null);



var q = new Queue;

q.addEventListener("enqueue", function (evt) {console.log("enqueue", evt);});
q.addEventListener("dequeue", function (evt) {console.log("dequeue", evt);});
q.addEventListener("empty", function (evt) {console.log("empty", evt);});

q.enqueue("the");   // "enqueue" Object {type: "enqueue", item: "the", target: Queue}
q.enqueue("quick"); // "enqueue" Object {type: "enqueue", item: "quick", target: Queue}
q.enqueue("brown"); // "enqueue" Object {type: "enqueue", item: "brown", target: Queue}
q.enqueue("fox");   // "enqueue" Object {type: "enqueue", item: "fox", target: Queue}

q.dequeue();        // "dequeue" Object {type: "dequeue", item: "the", target: Queue}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: "quick", target: Queue}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: "brown", target: Queue}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: "fox", target: Queue}
                    // "empty"   Object {target: Queue, type: "empty"}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: undefined, target: Queue}
                    // "empty"   Object {target: Queue, type: "empty"}

»Privileged Talent«

Jede andere, bisher noch nicht erfasste Smart Talent-Variante soll dann einfach Privileged Talent heißen, was sich ziemlich gut mit der letzten Definition deckt:

Jede Umsetzung einer Rolle, die entweder ausschließlich auf zusätzlich injizierten veränderlichen Zustand angewiesen ist oder aber auf beides, Erzeugung von veränderlichem Zustand und zusätzlich injizierten Zustand ungeachtet seiner Veränderlichkeit, soll als Privileged Talent bezeichnet werden.


Schlusskommentar

Das Ziel dieses Dokuments war es, nur auf der Sprachkernebene von JavaScript, Wissen über Techniken und Muster zur Vefügung zu stellen/zu verbreiten, die auf einfachem Wege zu besseren Ergebnissen bei Wartung und Wiederverwendung von Quellcode führen.
Vor ein paar Jahren konnte mich schon traits.js nicht vollständig überzeugen, und heutzutage gelingt CocktailJS dies auch nur ansatzweise. Meiner Meinung nach sind beide Bibliotheken nicht offen genug und in ihrer Leistungsfähigkeit deutlich überdimensioniert. Es sind eher schon kleine aber komplexe Frameworks.
Und beide wählen dazu nicht den hier beschriebenen und von mir immer favorisierten Funktions-basierten Ansatzt, dessen Vorteile darin liegen, dass man auf Sprachkernebene nicht nur die Delegation geschenkt bekommt, sondern darüber gleichzeitig auch noch Zustand injizieren und umherreichen kann.

Diese Arbeit hat hoffentlich verständlich genug gezeigt, dass es in JavaScript genügt, einen rein auf Fuktionsobjekten und Delegation beruhenden Ansatz, verpackt in ein »Module Pattern«, zu wählen, um Talent-basierte Komposition zu ermöglichen, die sich ausschließlich auf das Mixin-Muster sowie Trait-spezifische Funktionalität stützt.

Die meisten praxisnahen Anwendungsfälle in JavaScript benötigen wirklich nicht die viel zu komplexen Programmieransätze wie sie z.B. von den beiden erwähnten Bibliotheken verwendet werden. Der im Artikel propagierte Ansatze zur Nutzung Funktions-getriebener Talente-Muster macht den eigenen Code unabhängig von den Objektkompositions-Werkzeugen Dritter.

Die Beschränkung auf dieses eine Grundmuster, welches die Anwendung grundlegender Sprachkernmerkmale wie Function, call und apply sowie closure, context und scope nur variiert, sichert immer den sauberen Ansatz sowie schlanke und bedarfsgerecht umgesetzte Talente.


Anhang

unterstützender Quellcode:

Function.prototype.before = function (behaviorBefore, target) {
  var proceedAfter = this;

  return function () {
    var args = arguments;

    behaviorBefore.apply(target, args);
    return proceedAfter.apply(target, args);
  };
};
Function.prototype.after = function (behaviorAfter, target) {
  var proceedBefore = this;

  return function () {
    var args = arguments;

    proceedBefore.apply(target, args);
    return behaviorAfter.apply(target, args);
  };
};
Function.prototype.around = function (behaviorAround, target) {
  var proceedEnclosed = this;

  return function () {
    return behaviorAround.call(target, proceedEnclosed, behaviorAround, arguments, target);
  };
};

Function.modifiers.adviceTypes.before-after-around ist die tragfähigere Umsetzung aller oben bereitgestellten und vereinfachten Code-Varianten.


  • Dieses Dokument wird öffentlich über google drive und über blogspot geteilt. Jeder darf kommentieren und ist hiermit dazu aufgefordert. Eingeladene Leute haben zusätzlich Editier-Rechte.

  • Das Dokument gibt es auch als gist.


geschrieben mit StackEdit.


  1. de.wikipedia.org: JavaScript; en.wikipedia.org: JavaScript; developer.mozilla.org: JavaScript
  2. javascriptweblog.wordpress.com: »A fresh look at JavaScript Mixins«
  3. webreflection.blogspot.de: »Flight Mixins Are Awesome!«
  4. petsel.github.io: »JavaScript Code Reuse Patterns«
  5. github.com/petsel/javascript-code-reuse-patterns: »Composition«
  6. developer.mozilla.org: this keyword
  7. developer.mozilla.org: Function Object
  8. developer.mozilla.org: Inheritance and the prototype chain
  9. developer.mozilla.org: Function.prototype.call
  10. developer.mozilla.org: Function.prototype.apply
  11. Software Composition Group (SCG), University of Bern: Trait
  12. Software Composition Group (SCG), University of Bern
  13. SCG, University of Bern: »Traits: Composable Units of Behavior«
  14. SCG, University of Bern: »Stateful Traits«
  15. SCG, University of Bern: SCG: Talents
  16. SCG, University of Bern: »Talents: Dynamically Composable Units of Reuse«

2014-04-11

The-many-Talents-of-JavaScript

The many »Talents« of JavaScript

The many talents of JavaScript for generalizing Role Oriented Programming approaches like Traits and Mixins

TL;DR / Summary

  • Role-oriented programming is a structural programming paradigm targeting separation of concerns in object oriented programming.
  • »Mixin« and »Trait« are established terms and core features in programmig languages; both do meet the Role paradigm.
  • Mixin and Trait based composition predominantly targets a language’s class level.
  • Traits do provide composition operations in order to resolve conflicts, thus theirs composition precedence is not as relevant as with Mixins that only support linear order composition.
  • Behavior as provided with Mixins and Traits can not be aquired or lost at runtime.
  • As for Traits, for quite a while there was no real guideline for how to handle state.
  • Talents are an academical concept that enhances Role approaches like Trait and Mixin, for it does develop further some of theirs features but also harmonizes theirs characteristics and eases theirs usage in practice.
  • JavaScript easily adpots to Talents because of its core language concepts like »Closure« and »Delegation«.

Implicit and Explicit Delegation

JavaScript1 is a Delegation Language.

Functions as Roles (Traits and Mixins)

JavaScript natively supports2 3 4 5 various function based implementations of Role approaches like Traits and Mixins. Such a function defines additional behavior by at least one method bound to the this6 keyword within its function7 body. A Role then has to be delegated explicitly via call8 or apply9 to objects that are in need of additional behavior that is not shared via the prototype chain10.

var Enumerable_first = function () {
  this.first = function () {
    return this[0];
  };
};
var list = ["foo", "bar", "baz"];

console.log("(typeof list.first)", (typeof list.first)); // "undefined"

Enumerable_first.call(list); // explicit delegation

console.log("list.first()", list.first()); // "foo"

Object Composition and Inheritance

Whereas explicit function based delegation does cover object composition in JavaScript, implicit delegation already happens every time the prototype chain is walked in order to e.g. find a method that might be related to but is not directly owned by an object. Once the method was found, it gets called within this objects context. Thus inheritance in JavaScript is covered by a delegation automatism that is bound to the prototype slot of constructor functions.

var Enumerable_first_last = function () {
  this.first = function () {
    return this[0];
  };
  this.last = function () {
    return this[this.length - 1];
  };
};
console.log("(typeof list.first)", (typeof list.first));  // "function"   // as expected
console.log("(typeof list.last)", (typeof list.last));    // "undefined"  // of course

Enumerable_first_last.call(Array.prototype);  // applying behavior to [Array.prototype]

console.log("list.last()", list.last());      // "baz"  // due to delegation automatism

»Mixins«

According to the English version of Wikipedia Flavors, …

… an early object-oriented extension to Lisp
, was the first programming language to include mixins.

Thus introducing code reuse across class hierarchies. Summing it up very briefly; even though Mixin approaches have been evolved over time, theirs composition mostly is limited to classes (Ruby does allow composition at instance level). And due to the lack of composition operators that otherwise support conflict resolution of rivaling (identically named) methods, Mixins need to be composed in linear order.

»Traits«

Traits11 have been extensively researched and developed within a decade starting as far back as November 200212 at the »Software Composition Group«13 (SCG), University of Bern.

The concept of Traits overcomes the aforementioned limitations of composition with Mixins, making the latter more flexible since Traits provide composition operators for combination, exclusion or aliasing of methods. And because Traits in one of theirs later development cycle (2006) are allowed to be stateful14 (the earliest SCG Traits were not allowed carrying state), JavaScript’s language features and its pure function based adoption of the Traits concept were meant for each other at least for the last couple of years.

Being a classless, almost entirely object based, in many ways highly dynamic language, capable of a 2 way delegation and acting out its at least partly functional nature, JavaScript can reach beyond the bounderies of Traits as well. The concept of the latter e.g. does not support behavioral changes of objects at runtime. Even though Trait composition is very flexible its mechanics target classes only. Traits also can not be added to or removed from classes at runtime.

But there is a not anymore that new kid on the block15 facing JavaScript at eye level. Now it is JavaScript’s turn to playfully explore it and also to evolve growth from a discovering journey that just has started …

»Talents: Dynamically Composable Units of Reuse«

The identically named SCG paper from July 2011 introduces Talents16 as …

… object-specific units of reuse which model features that an object can acquire at run-time. Like a trait, a talent represents a set of methods that constitute part of the behavior of an object. Unlike traits, talents can be acquired (or lost) dynamically. When a talent is applied to an object, no other instance of the object’s class are affected. Talents may be composed of other talents, however, as with traits, the composition order is irrelevant. Conflicts must be explicitly resolved.

Like traits, talents can be flattened, either by incorporating the talent into an existing class, or by introducing a new class with the new methods. However, flattening is purely static and results in the loss of the dynamic description of the talent on the object. Flattening is not mandatory, on the contrary, it is just a convenience feature which shows how traits are a subset of talents.

This pretty much sounds like a perfect match; as though the concept of Talents was made just in order to meet JavaScript’s language design and to fit its capabilities in means of implementing Talents in this languages most natural way. Composition can be achieved in both ways, at object (instance) and constructor (class) level. And Talents acknowledge state.

From »Mixins« to »Traits« - the JavaScript mechanics behind pure function based »Talents«

Talents by theirs scientific research results are supposed to work on both instance level and class level. Talents might be used as straightforward as Mixins but also can provide additional functionality that covers composition operators in order to be used like Traits.

The »Mixin Approach«

The most fine grained Talent implements a sole method without introducing state and without conflict resolution. For JavaScript one could think of an implementation of the already abovementioned Enumerable_first behavior which in its first iteration might look like the following code example:

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

Applying this Talent onto an array will provide the first ones behavior to the latter …

var list = ["foo", "bar", "baz"];

Enumerable_first.call(list);

console.log("list.first()", list.first()); // "foo"

… and operating at “class” level (Array.prototype), without conflict resolution, comes as close to a Mixin as possible …

var list = ["foo", "bar", "baz"];

Enumerable_first.call(Array.prototype);

console.log("list.first()", list.first()); // "foo"

The usage of this pattern at both “class” level and “instance” level was rediscovered by Angus Croll in 2011 and named Flight Mixin.

Even though this first approach works, it has some shortcomings that need to be improved. Right now every object the Talent was applied to, features its very own first method.

delete Array.prototype.first;

var
  list = ["foo", "bar", "baz"],
  arr = ["biz", "buzz"]
;
Enumerable_first.call(list);
Enumerable_first.call(arr);

console.log("arr.first()", arr.first()); // "biz"
console.log("(list.first === arr.first)", (list.first === arr.first)); // false

In order to make the first behavior shared code, the Talent implementation has to be wrapped into a module pattern - second code iteration:

var Enumerable_first = (function () {

  var
    Mixin,

    first = function () {
      return this[0];
    }
  ;
  Mixin = function () {
    this.first = first;
  };

  return Mixin;

}());

var
  list = ["foo", "bar", "baz"],
  arr = ["biz", "buzz"]
;
Enumerable_first.call(list);
Enumerable_first.call(arr);

console.log("list.first()", list.first());  // "foo"
console.log("arr.first()", arr.first());    // "biz"

console.log("(list.first === arr.first)", (list.first === arr.first)); // true

Having proved the last approach, it can be shortened within a third iteration to …

var Enumerable_first = (function () { // Pure Talent / Lean Mixin.
  var first = function () {
    return this[0];
  };
  return function () { // Mixin mechanics refering to shared code.
    this.first = first;
  };
}());

»Pure Talent« and »Lean Mixin«

Having come that fare one could phrase …

Every implementation of a Role Pattern in JavaScript should be called Talent.

… and …

Every module based stateless implementation of a Role that supports code sharing and does not provide conflict resolution should be referred to as Pure Talent and/or Lean Mixin

»Trait« bases evolved from »Mixin« approaches

A logical next step was to provide conflict resolution by a Resolvable Lean Mixin that implements composition methods like resolveBefore, resolveAfter, resolveAround and resolveWithAlias.

Due to having to resolve conflicts of competing/identical methods some additional supporting source code needs to be provided in order to make the given examples run without failing.

The code of a Resolvable approach based on a Lean Mixin then most probably will look like the next given example:

var Resolvable = (function () { // Pure Talent / Lean Mixin.

  var
    Mixin,

    isString    = function (type) {
      return (typeof type == "string");
    },
    isFunction  = function (type) {
      return (typeof type == "function");
    },

    resolveBefore = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].before(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAfter = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].after(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAround = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].around(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },

    resolveWithAlias = function (methodName, aliasName, rivalingMethod) {
      if (
        isString(methodName)
        && isString(aliasName)

        && (methodName != aliasName)

        && isFunction(rivalingMethod)
      ) {
        this[aliasName] = rivalingMethod;
      }
    }
  ;

  Mixin = function () { // Mixin mechanics refering to shared code.
    var type = this;

    type.resolveBefore    = resolveBefore;
    type.resolveAfter     = resolveAfter;
    type.resolveAround    = resolveAround;
    type.resolveWithAlias = resolveWithAlias;
  };

  return Mixin;

}());

The Resolvable’s use case for example then might look like this:

var Floatable = function () { // implementing a [Floatable] Trait.

  this.resolveAfter("initialize", function () {
    console.log("make it floatable.");
  });
};
var Ridable = function () { // implementing a [Ridable] Trait.

  this.resolveBefore("initialize", function () {
    console.log("make it ridable.");
  });
};

var Amphicar = function () {  // implementing an [Amphicar] Constructor.

  Resolvable.call(this);      // applying the [Resolvable] Mixin.

  Floatable.call(this);       // applying the [Floatable] Trait.
  Ridable.call(this);         // applying the [Ridable] Trait.

  this.initialize();
};

var ac = new Amphicar;
// "make it ridable."
// "make it floatable."

console.log(Object.keys(ac));
// ["resolveBefore", "resolveAfter", "resolveAround", "resolveWithAlias", "initialize"]

As one can see the purely implemented Resolvable Talent results in 4 accessible methods an Amphicar instance really does not need to expose.

Yet, the pure function based delegation approach in JavaScript is sustainable and capable of providing a solution that still implements all of a Resolvable’s composition methods whilst keeping them private.

What could be called a Hidden/Undercover Talent or Silent/Stealth Mixin does not mutate the object it is working on. Its main goal is preserving this objects context and delegation of this context to behavior that never will be exposed into public. For this such a Mixin augments an injected (local) proxy object by the aforementioned delegating behavior.

The »Stealth Mixin« approach of the Resolvable_silent variant will hide its methods from any Amphicar instance as it will be immediately demonstrated:

var Resolvable_silent = (function () { // Undercover Talent / Stealth Mixin.

  var
    StealthMixin,

    isString    = function (type) {
      return (typeof type == "string");
    },
    isFunction  = function (type) {
      return (typeof type == "function");
    },

    resolveBefore = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].before(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAfter = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].after(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },
    resolveAround = function (methodName, rivalingMethod) {
      if (isString(methodName) && isFunction(rivalingMethod)) {

        var type = this;
        if (isFunction(type[methodName])) {

          type[methodName] = type[methodName].around(rivalingMethod, type);
        } else {
          type[methodName] = rivalingMethod;
        }
      }
    },

    resolveWithAlias = function (methodName, aliasName, rivalingMethod) {
      if (
        isString(methodName)
        && isString(aliasName)

        && (methodName != aliasName)

        && isFunction(rivalingMethod)
      ) {
        this[aliasName] = rivalingMethod;
      }
    }
  ;

  StealthMixin = function (resolvableProxy) { // Stealth Mixin using an injected proxy.
    if (resolvableProxy && (typeof resolvableProxy == "object")) {

      var type = this;
  /*
   *  special functional Mixin pattern that preserves and foldes
   *  two different contexts and does delegation with partly code reuse.
   */
      resolvableProxy.resolveBefore    = function (/*methodName, rivalingMethod*/) {
        resolveBefore.apply(type, arguments);
      };
      resolvableProxy.resolveAfter     = function (/*methodName, rivalingMethod*/) {
        resolveAfter.apply(type, arguments);
      };
      resolvableProxy.resolveAround    = function (/*methodName, rivalingMethod*/) {
        resolveAround.apply(type, arguments);
      };
      resolvableProxy.resolveWithAlias = function (/*methodName, aliasName, rivalingMethod*/) {
        resolveWithAlias.apply(type, arguments);
      };
    }
  };

  return StealthMixin;

}());

The already given Resolvable’s use case from above has to be slightly changed to …

var Floatable = function () { // implementing a "silent" [Floatable] Trait featuring ...
                              // ... the "Stealth Mixin" variant of [Resolvable].
  var resolvable = {};
  Resolvable_silent.call(this, resolvable);

  resolvable.resolveAfter("initialize", function () {
    console.log("initialize :: make this floatable.", this);
  });
};
var Ridable = function () { // implementing a "silent" [Ridable] Trait featuring ...
                            // ... the "Stealth Mixin" variant of [Resolvable].
  var resolvable = {};
  Resolvable_silent.call(this, resolvable);

  resolvable.resolveBefore("initialize", function () {
    console.log("initialize :: make this ridable.", this);
  });
};

var Amphicar = function () {  // implementing the [Amphicar] Constructor.

  Floatable.call(this);       // applying the "silent" [Floatable] Trait.
  Ridable.call(this);         // applying the "silent" [Ridable] Trait.

  this.initialize();
};

var ac = new Amphicar;
// initialize :: make this ridable.   Amphicar {initialize: function}
// initialize :: make this floatable. Amphicar {initialize: function}

console.log("the new amphicar", ac);
// the new amphicar                   Amphicar {initialize: function}

… and does prove the function based concepts of Stealth Mixins and Silent Traits.

»Undercover Talent«, »Stealth Mixin« and »Silent Trait«

Every implementation of a Role that does not mutate its this context but augments an additionally injected proxy with behavior that folds delegation and both contexts in order to not directly expose the behavior into public should be referred to as Hidden/Undercover Talent and/or Silent/Stealth Mixin.

.

There are implementations of conflict resolving composition methods that are based on the Undercover Talent / Stealth Mixin pattern, called Resolvable.

.

Every implementation of a Role that is a composite of at least one Talent and one Stealth Mixin based Resolvable should be referred to as Silent Trait.

A current Pattern Taxonomy of function based »Talents«

Focusing on state its availability and origin, and how state then gets mutated or augmented whilst providing real world JavaScript examples, will be the guidline for the rest of this paper for it might be the most helpful way for catching up on this special matter.

»Taxonomy Matrix« of some possible Talent Patterns

Earlier papers and presentations of mine came up with kind of a »Trait Taxonomy Matrix« for the many Trait / Mixin variations, as a direct result of trying to classify the most used Role implementations in JavaScript, I derieved from practice.

Since the usage of terms like »Trait« and »Mixin« does not fully cover what really can be achieved with JavaScript, »Talents« as concept and term has become the new working base for all of those already identified code reuse variants. Based on the following state coordinates, the most recent wording therefore had to be changed completely to …

state stateless no active mutation actively mutating proxy augmentation
stateless Pure Talent
Lean Mixin
- - -
created - Smart Talent
(Skilled Talent)
Smart Talent
(Skillful Talent)
-
injected - Smart Talent
(Promoted Talent)
Smart Talent
(Privileged Talent)
-
both created and injected - Smart Talent
(Privileged Talent)
Smart Talent
(Privileged Talent)
-
injected proxy - - - Hidden/Undercover Talent
Silent/Stealth Mixin

The provided »Taxonomy Matrix« is a moving target and the currently used taxonomy is encouraged to be reviewed by everyone who is or feels opinionated about this topic.

»Smart Talent« Patterns

Whereas the Pure Talent is not aware of state at all and a Hidden/Undercover Talent has its own strict definition, every variation of a Smart Talent needs to carry state, be it created within itself and/or injected into it, actively mutating its state(s and/) or even not.

Every implementation variant of a Role that carries state regardless if injected or self defined and regardless of such states mutability should be referred to as Smart Talent.

There will be more than 6 variants of the Smart Talent pattern. And it heavily has to be overthought and discussed if it is really meaningful and of any use to distinguish and name all these variants. But there are different use cases for at least 2 of them.

An implementation of a Role that relies on additionally injected state but does only read and never does mutate it should be referred to as Promoted Talent.

Practical use cases then probably might be firstly a wrapper for implementing customized enumerable lists, Enumerable_first_last_item_listWrapper

var Enumerable_first_last_item_listWrapper = (function () {

  var
    global = this,

    Talent,

    parse_float = global.parseFloat,
    math_floor  = global.Math.floor
  ;
  Talent = function (list) { // implementing the "promoted" [Enumerable] Talent.
    var
      enumerable = this
    ;
    enumerable.first = function () {
      return list[0];
    };
    enumerable.last = function () {
      return list[list.length - 1];
    };
    enumerable.item = function (idx) {
      return list[math_floor(parse_float(idx, 10))];
    };
  };

  return Talent;

}).call(null);

… and secondly Allocable, a wrapper too, that controls outside access of enclosed list structures, and that already makes use of the firstly provided wrapper example …

var Allocable = (function () {

  var
    global = this,

    Enumerable_listWrapper = global.Enumerable_first_last_item_listWrapper,

    Talent,

    Array = global.Array,

    array_from = ((typeof Array.from == "function") && Array.from) || (function (proto_slice) {
      return function (listType) {

        return proto_slice.call(listType);
      };
    }(Array.prototype.slice))
  ;

  Talent = function (list) { // implementing the "promoted" [Allocable] Talent.
    var
      allocable = this
    ;
    allocable.valueOf = allocable.toArray = function () {
      return array_from(list);
    };
    allocable.toString = function () {
      return ("" + list);
    };
    allocable.size = function () {
      return list.length;
    };
    Enumerable_listWrapper.call(allocable, list);
  };

  return Talent;

}).call(this);

.., thus both together already do provide the base for e.g custom collection types like Queue:

var Queue = (function () {

  var
    global = this,
    Allocable = global.Allocable
  ;

  return function () { // implementing the [Queue] Constructor.
    var list = [];

    this.enqueue = function (type) {

      list.push(type);
      return type;
    };
    this.dequeue = function () {

      return list.shift();
    };

    Allocable.call(this, list); // applying the "promoted" [Allocable] Talent.
  };

}).call(null);



var q = new Queue;

console.log("q.size()", q.size());        // 0

q.enqueue("the");
q.enqueue("quick");
q.enqueue("brown");
q.enqueue("fox");

console.log("q.size()", q.size());        // 4
console.log("q.toArray()", q.toArray());  // ["the", "quick", "brown", "fox"]

console.log("q.first()", q.first());      // "the"
console.log("q.item(1)", q.item(1));      // "quick"
console.log("q.last()", q.last());        // "fox"

q.dequeue();
q.dequeue();

console.log("q.toArray()", q.toArray());  // ["brown", "fox"]
console.log("q.size()", q.size());        // 2

q.dequeue();
q.dequeue();
q.dequeue();

console.log("q.size()", q.size());        // 0

»Skilled Talent« and »Skillful Talent«

An implementation of a Role that does create mutable state on its own in order to solve its task(s) but does never rely on additionally injected state should be referred to as Skilled Talent or Skillful Talent

Observable probably is one of the most common real world examples for Mixins. The given example’s implementation is a Skillful Talent for it will at apply time create, read and mutate various states:

var Observable_SignalsAndSlots = (function () { // implementing a "skillful" [Observable] Talent.

  var
    global = this,
    Array = global.Array,

    isFunction = function (type) {
      return (
        (typeof type == "function")
        && (typeof type.call == "function")
        && (typeof type.apply == "function")
      );
    },
    isString = (function (regXCLassName, expose_implementation) {
      return function (type) {

        return regXCLassName.test(expose_implementation.call(type));
      };
    }((/^\[object\s+String\]$/), global.Object.prototype.toString)),

    array_from = ((typeof Array.from == "function") && Array.from) || (function (proto_slice) {
      return function (listType) {

        return proto_slice.call(listType);
      };
    }(Array.prototype.slice))
  ;

  var
    Event = function (target, type) {

      this.target = target;
      this.type = type;
    },

    EventListener = function (target, type, handler) {

      var defaultEvent = new Event(target, type);             // creation of new state.

      this.handleEvent = function (evt) {
        if (evt && (typeof evt == "object")) {

          evt.target = defaultEvent.target;
          evt.type = defaultEvent.type;

        } else {

          evt = {
            target: defaultEvent.target,
            type: defaultEvent.type
          };
        }
        handler(evt);
      };
      this.getType = function () {
        return type;
      };
      this.getHandler = function () {
        return handler;
      };
    },

    EventTargetMixin = function () { // implementing the [EventTarget] Mixin as "skillful" Talent.

      var eventMap = {};                                      // mutable state.

      this.addEventListener = function (type, handler) {      // will trigger creation of new state.
        var reference;
        if (type && isString(type) && isFunction(handler)) {
          var
            event = eventMap[type],
            listener = new EventListener(this, type, handler) // creation of new state.
          ;
          if (event) {
            var
              handlers = event.handlers,
              listeners = event.listeners,
              idx = handlers.indexOf(handler)
              ;
            if (idx == -1) {
              handlers.push(listener.getHandler());
              listeners.push(listener);

              reference = listener;
            } else {
              reference = listeners[idx];
            }
          } else {
            event = eventMap[type] = {};
            event.handlers = [listener.getHandler()];
            event.listeners = [listener];

            reference = listener;
          }
        }
        return reference;
      };

      this.dispatchEvent = function (evt) {
        var
          successfully = false,
          type = (
            (evt && (typeof evt == "object") && isString(evt.type) && evt.type)
            || (isString(evt) && evt)
          ),
          event = (type && eventMap[type])
        ;
        if (event) {
          var
            listeners = (event.listeners && array_from(event.listeners)),
            len = ((listeners && listeners.length) || 0),
            idx = -1
          ;
          if (len >= 1) {
            while (++idx < len) {

              listeners[idx].handleEvent(evt);
            }
            successfully = true;
          }
        }
        return successfully;
      };
    }
  ;

  return EventTargetMixin; // will be exposed to the outside as [Observable_SignalsAndSlots].

}).call(null);

The already provided Queue example in its next iteration step then could be improved to:

var Queue = (function () {

  var
    global = this,

    Observable  = global.Observable_SignalsAndSlots,
    Allocable   = global.Allocable,

    Queue,

    onEnqueue = function (queue, type) {
      queue.dispatchEvent({type: "enqueue", item: type});
    },
    onDequeue = function (queue, type) {
      queue.dispatchEvent({type: "dequeue", item: type});
    },
    onEmpty = function (queue) {
      queue.dispatchEvent("empty");
    }
  ;

  Queue = function () { // implementing the [Queue] Constructor.
    var
      queue = this,
      list = []
    ;
    queue.enqueue = function (type) {

      list.push(type);
      onEnqueue(queue, type);

      return type;
    };
    queue.dequeue = function () {

      var type = list.shift();
      onDequeue(queue, type);

      (list.length || onEmpty(queue));

      return type;
    };
    Observable.call(queue);       // applying the "skillful" [Observable_SignalsAndSlots] Talent.
    Allocable.call(queue, list);  // applying the "promoted" [Allocable] Talent.
  };

  return Queue;

}).call(null);



var q = new Queue;

q.addEventListener("enqueue", function (evt) {console.log("enqueue", evt);});
q.addEventListener("dequeue", function (evt) {console.log("dequeue", evt);});
q.addEventListener("empty", function (evt) {console.log("empty", evt);});

q.enqueue("the");   // "enqueue" Object {type: "enqueue", item: "the", target: Queue}
q.enqueue("quick"); // "enqueue" Object {type: "enqueue", item: "quick", target: Queue}
q.enqueue("brown"); // "enqueue" Object {type: "enqueue", item: "brown", target: Queue}
q.enqueue("fox");   // "enqueue" Object {type: "enqueue", item: "fox", target: Queue}

q.dequeue();        // "dequeue" Object {type: "dequeue", item: "the", target: Queue}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: "quick", target: Queue}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: "brown", target: Queue}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: "fox", target: Queue}
                    // "empty"   Object {target: Queue, type: "empty"}
q.dequeue();        // "dequeue" Object {type: "dequeue", item: undefined, target: Queue}
                    // "empty"   Object {target: Queue, type: "empty"}

»Privileged Talent«

Every other remaining Smart Talent variant then should be called Privileged Talent which pretty much gets covered by the last definition:

An implementation of a Role that relies either on mutation of additionally injected state only or on both, creation of mutable state and additionally injected state, regardless if the latter then gets mutated or not, should be referred to as Privileged Talent.


Closing Words

The goal of this paper was to provide/share knowledge of code reuse patterns in JavaScript at this languages core level. Neither traits.js some years ago could convince me nor does it fully CocktailJS for both are, in my eyes, overambitious closed/monolithic libraries that do follow an object based approach, whereas I always strongly will argue that a function based approach is most natural for this languages design since it does provide delegation for free and at the same time also enables injecting of and passing around state.

The paper hopefully could prove that for JavaScript a pure function and delegation based approach, mostly accompanied by the module pattern, already enables Talent based composition, built upon Mixin mechanics and Trait specific functionality.

Most of real world JavaScript use cases are really not in need for far more complex programming approaches like being used by each of both aforementioned libraries. And making use of function based Talent patterns keeps ones code framework and library agnostic.

Staying self-restraint to only one base patterns that just varies the usage of core language features like Function, call and apply as much as closure, context and scope ensures a clean approach and results in lean and tailored Talent implementations.


Appendix

supporting source code:

Function.prototype.before = function (behaviorBefore, target) {
  var proceedAfter = this;

  return function () {
    var args = arguments;

    behaviorBefore.apply(target, args);
    return proceedAfter.apply(target, args);
  };
};
Function.prototype.after = function (behaviorAfter, target) {
  var proceedBefore = this;

  return function () {
    var args = arguments;

    proceedBefore.apply(target, args);
    return behaviorAfter.apply(target, args);
  };
};
Function.prototype.around = function (behaviorAround, target) {
  var proceedEnclosed = this;

  return function () {
    return behaviorAround.call(target, proceedEnclosed, behaviorAround, arguments, target);
  };
};

Function.modifiers.adviceTypes.before-after-around is a more sustainable implementation of the just provided basic code variant.


  • This document is shared in public via google drive and via blogspot. Everyone is allowed commenting on it. Invited people even have edit rights.

  • It also is available as gist.


Written with StackEdit.


  1. en.wikipedia.org: JavaScript; developer.mozilla.org: JavaScript
  2. javascriptweblog.wordpress.com: »A fresh look at JavaScript Mixins«
  3. webreflection.blogspot.de: »Flight Mixins Are Awesome!«
  4. petsel.github.io: »JavaScript Code Reuse Patterns«
  5. github.com/petsel/javascript-code-reuse-patterns: »Composition«
  6. developer.mozilla.org: this keyword
  7. developer.mozilla.org: Function Object
  8. developer.mozilla.org: Function.prototype.call
  9. developer.mozilla.org: Function.prototype.apply
  10. developer.mozilla.org: Inheritance and the prototype chain
  11. Software Composition Group (SCG), University of Bern: Trait
  12. SCG, University of Bern: »Traits: Composable Units of Behavior«
  13. Software Composition Group (SCG), University of Bern
  14. SCG, University of Bern: »Stateful Traits«
  15. SCG, University of Bern: SCG: Talents
  16. SCG, University of Bern: »Talents: Dynamically Composable Units of Reuse«