Blog-Archiv

Samstag, 20. Januar 2018

ES6 Mixin Trees

ES6: ?

ES6 mixin is a concept that allows you to build together a class from a lot of very small reusable units of logic. A mixin is not a normal class. You use mixins via factories, and such a factory receives a given class and returns a "mixin class" that extends the given class, all done at runtime.

const A = (SuperClass = Object) => class extends SuperClass
{
}

That means, the result of a call to mixin-factory A is a sub-class of given SuperClass! In case no SuperClass is given, it would be Object. Anyway, when working with mixins it is not important to think in terms of inheritance any more. Just when methods or properties of same name occur within the mixed classes, the extension order plays a role. What about overrides? Because a mixin never knows what class it will be extending, it shouldn't do overrides, except when its definition itself includes a mix() call (as shown by the "Complex Tree" example below).

There are lots of implementations around how to mix classes. In this Blog I want to present a function that merges a class with a list of mixins.

The Mix-Function

Using this function you won't have to extend by combining the factories directly with each other, like in

class ABC extends A(B(C()))
{
}

Instead you give a comma-separated list of things to extend:

class ABC extends mix(A, B, C)
{
}

A can be a class or a factory, but both B and C must be mixin-factories. That means, there can be only one concrete super-class in a mix() call, all other parameters (except the first) must be mixin-factories.

Here is the mix() function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    /**
     * Mixin inheritance builder.
     * @param SuperClassOrFactory required, the basic super-class that given factories
     *     should extend, or a factory in case no basic class to extend is needed.
     * @param classFactories optional, arbitrary number of arguments that are class-factories.
     * @return a class created by extending SuperClass (when given) and all classes created
     *     by given factories.
     */
    function mix(SuperClassOrFactory, ...classFactories) {
        const superClassIsFactory = (SuperClassOrFactory.prototype === undefined)
        const factories = superClassIsFactory ? [ SuperClassOrFactory, ...classFactories ] : classFactories
        let resultClass = superClassIsFactory ? undefined : SuperClassOrFactory
        
        /* "last wins" override strategy */
        for (const classFactory of factories) {
            if (classFactory.prototype !== undefined)
                throw new Error("Class factory seems to be a class: "+classFactory)
                
            resultClass = classFactory(resultClass)
        }
        return resultClass
    }

This mixes together an optional super-class with a list of class-factories (mixin-factories). It returns a class that extends that optional first parameter, and all mixins created by factories.

The first parameter SuperClassOrFactory can not be undefined, it gives the basic super-class, or a factory in case no basic class is needed (mixins-only mix). The second parameter ...classFactories is a spread-expression denoting all remaining parameters, which is a list of class-factories.

Why is this first parameter needed? Study the "Small Tree" example below. You will find that not only classes want to extend several mixins, also mixins want to extend several other mixins. But a mixin has, other than a class, the parameter SuperClass it needs to satisfy. Thus all mixins will pass their SuperClass as first parameter to their own mix() call!

Initially the function checks whether SuperClassOrFactory is a factory or a class. The distinction criterion is that a class always has a prototype. When is it a factory, the result-class is set to undefined, while the factory is inserted into the list of class-factories. Otherwise the result-class is set to be the first parameter.

The subsequent loop takes every class-factory and calls it with the result-class as super-class. Whatever the factory returns is assigned to the result-class again. This gives a chain of extensions where the last mixin will be the lowest sub-class, prevailing over any methods or properties of same name in super-classes ("last wins" principle).

Finally the result-class is returned. Called with just one parameter which is a class, this function would return just that class, called with a factory, it would return the factory's return. Remember that a factory creates a class extending a given super-class.

Mind that this function is the only way how mixins can extend 1-n other mixins by definition (the mixin specifies its super-mixins). This is needed when a mixin depends on another mixin, see "Small Tree" example.

Test Scripts

Click onto one of the left-side buttons to see an example script and its description. Below you find an output area for console.log() messages. You can also edit the script and execute it again by using the "Execute Script" button.

Description
Output

Resume

I don't believe that ES6 mixins ever will be statically analyzable. That means you will not be able to use static type checks for your ES6 mixins, like it was promised recently for normal ES6 classes.

Another thing to consider with mixins is that private fields in a constructor will not work, because they are bound to the constructor function scope, and then the diamond-problem hits you. If you want privacy for mixins, you should move each of them into its own module and do it by non-exported variables and functions. But mind that these will not be instance-bound!

Using constructors in mixins generally is not recommendable. The diamond-problem will hit you, because mixins can be combined in many and unpredictable ways. And as soon as you have a mixin two times in an inheritance tree, its constructor also will be called two times. (For example, don't install mouse- or keyboard-listeners in mixin constructors!)

ES6 mixins look really seducing. You can weave in any aspect that you need on a class. The portion of reusable code in your project may increase dramatically. It is an unfamiliar technique for an OO programmer used to single inheritance, but it offers interesting new possibilities.




This is a short example of what was explained in the introduction. Just two independent mixin-factories Being and Movable get combined into a class Animal.

Mind that any mixin-class that has a constructor must call super(args) with its own parameters, because it doesn't know its super-class, and the ES6 interpreter would throw an error when extending a class but not calling super().

The console-output shows that both properties species and location are present in the resulting class Animal.

This example shows that a mixin can define its super-mixins by definition, even several ones.

The final result class ActualAnimal is mixed together from two mixins Animal and Born, and Animal is again built together from three items: it mixes, by definition, its dynamically passed super-class with Soundable and Movable. Mind that you always need to pass SuperClass as first parameter to the mix() call of a mixin-factory like Animal!

This example also provides a common Mixin super-class. It could hold functionality that you want to be in every mixin-instance, for example a unique id. Use Mixin as default super-class in every factory-definition instead of Object.

The "Diamond Problem" describes the ambiguity that occurs when you mix two classes that both have an instance-field or method with same name. In the example below, both Mars and Saturn derive Base, and all three of them define a property identity and a method getId().

Which one will survive? The loop direction of function mix() makes the last win. When you change it to for (const classFactory of factories.reverse()), then Mars will prevail. You can have the same effect when changing the extends-clause to extends mix(Saturn, Mars).

Please mind that Base constructor is called twice, once from Mars and once from Saturn. If you install event listeners in a mixin constructor, they will be installed as often as that mixin has been used as super-mixin!

This example shows that everything works even on a bigger scale. Here is the UML class diagram of this inheritance tree:

Freitag, 19. Januar 2018

ES6 Private Class Field Workarounds

ES6: ?

ES6 classes provide no privacy in the sense of access modifiers. In other words, encapsulation lacks. The ES6 tutorial proposes four ways to achieve privacy, from which one is not real, because it is just the convention to use _underscore as prefix for private variables. Which would not prevent corruption in any way.

The other three ways, in my opinion, are actually not usable in real world. This Blog presents them in examples, and tries to show up their costs, so that you can decide this on your own.

Test Scripts

Click onto one of the left-side buttons to see an example script and its description. Below you find an output area for console.log() messages. You can also edit the script and execute it again by using the "Execute Script" button.

Description
Output


Resume

ES6 encapsulation is strongly connected to modules, there you can have invisible functions outside of an exported class. The restriction is that these must receive all their information via parameters, because they are not connected to any object-instance, they are like Java static.




This is definitely the best, safest and most general solution if you absolutely want ES6 privacy for any price.

The class CountDown defines a private variable counter in its constructor. This variable exists just in the block-scope of the constructor, but it survives the constructor execution due to functions increment(), decrement() and getValue() referencing it. But it is not accessible by any class-method outside the constructor! Mind that this counter can not be a class-property, because it would be public then by default.

The ugly thing about this solution is that all class-methods that deal with private fields have to be implemented inside the constructor body, and all private fields also have to be there. Thus we lose the elegant ES6 syntax for class methods. Because most public methods will want to call private ones, or use private fields, everything, the whole class body, has to go inside the constructor!

The nice thing about this solution is that you can also define private functions inside the constructor body. Just don't put them onto the this pointer, do it like reset().

This is a solution that works just when your class is encapsulated in an ES6 module.

For each private field of the class you need one WeakMap. The constructor, or any setter, stores the value of a field into the according map, using this as key. Any getter would read it from the map the same way.
We could regard this as an hidden internal instance management, to be stereotyped in every class that needs privacy.

Extremly ugly is the necessity to have one map for each field. Further you can not have private methods with this solution.

Why is the instance-management done with WeakMap? When the class instance, which is the map-key, wouldn't be referenced any more, it and all its field values would disappear by garbage-collection. A normal Map ould never release the object!

This is also a solution that works just when your class is encapsulated in an ES6 module.

For each private field of the class you need one Symbol value. The constructor, or any setter, stores the value of a field into a symbol-property onto the this object. Getters read it the same way. Although it is public, no outside code can read the property, because it can not know the symbol.
(Except when it reads the symbols by reflection via Object.getOwnPropertySymbols(person), but it could not guess the property's semantic, by no comparison like if (symbol === "firstName") ....)

Donnerstag, 11. Januar 2018

ES6 Multiple Inheritance via Mixins

ES6: ?

The discussion about multiple inheritance has been going on for a long time. I consider Java interfaces to be a clean and resilient solution. It allows multiple inheritance for units that don't contain implementations, thus no instance field duplication or method shadowing can occur. Moreover interfaces are a design tool, i.e. you can specify software without implementing it!
Nevertheless, following the sign of times, Java 8 allows interfaces to also contain implementations, but in a restricted way. Other languages like C# and Scala always allowed multiple inheritance. Scala provides traits, comparable to Java's abstract classes, and follows a "last wins" strategy concerning method shadowing.

Basically this discussion exists just because OO inheritance exists. Inheritance is the most elegant way to reuse code. Its sibling delegation is kind of code-duplication, and recommendable just where inheritance can't work, e.g. because a super-class already exists. That's the problem. We statically bind classes to super-classes. Then we would like to reuse them in another context, which is not possible due to that super-class. So what exactly would we like to do?

We would like to combine pieces of code freely with each other. We'd like to attach a Colorable and a Shadowable decoration to a PushButton component. Tomorrow maybe we also want it to be Borderable.

Aspect-oriented programming languages already exist, but they are currently just around other languages, jumping in for cross-cutting concerns. That's not the way we think normally, communicating and understanding mostly happens hierarchically, like in a document's table-of-contents.

Mixins

The ES6 world talks about "mixins". That's an OO term designating the classes involved in multiple inheritance. There are a lot of different ES6 multiple inheritance implementations around on the web, but none really works well. The official proposal works, but it requires mixin-classes to be written in a special way. That is what I want to look at in this Blog.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    const One = (SuperClass = Object) => class extends SuperClass
    {
        sayOne() {
            return "One"
        }
    }

    const Two = (SuperClass = Object) => class extends SuperClass
    {
        sayTwo() {
            return "Two"
        }
    }
  
    class OneTwo extends One (Two ())  /* extends One, Two */
    {
    }
  
    const oneTwo = new OneTwo()
  
    console.log(
        "sayOne(): "+oneTwo.sayOne()+"\n"+
        "sayTwo(): "+oneTwo.sayTwo()
    )

This is a minimal example for a class extending two others, or let's say mixing-in two others. Please refer to source code below for further explanations.

Mind that when using modules, you can write this differently, and give the mixin a non-duplicated class name:

export default (SuperClass = Object) => class One extends SuperClass
{
    ....
}

To be imported by:

import One from "./one.js"
import Two from "./two.js"

class OneTwo extends One (Two ())
{
    ....
}

Examples

Click onto one of the buttons to get an example script into the text area. Below the script you find an output area for console.log() statements, and on the very bottom there is an explanation of the example.

  •  
  •  
  •  
  •  
  •  
Output
Description

Resume

  • One is a factory (mixin class factory)
  • One() returns a new class (mixin class)
  • OneTwo is a class extending mixin classes created by mixin factories at runtime

Some big questions remain open that really endanger practical usability of the mixin concept:

  • How can a mixin extend a certain other mixin it depends on? For example, a Shadowable mixin requires a MouseObserver mixin being present, how can we ensure that the final mixed class actually contains a MouseObserver?

  • How can a mixin extend several other mixins it depends on? Currently just classes can mix them together. What we also need is to merge several mixins into a parent-mixin!

Multiple inheritance will stay under discussion. The central challenge here is to find a better way for code reuse than inheritance, which is clean only as single inheritance.




A minimal example for inheritance chaining with mixins.

A mixin class factory is defined by an expression like const A = (SuperClass) => class extends SuperClass. The factory-name is A (not the class name!). The class-creation behind "=" happens through an "arrow-class", which is basically the same as an "arrow-function". I have put parentheses around it to make it clearer, although they are not needed. (SuperClass) is the parameter for the arrow-class. Mind that SuperClass is NOT a keyword, it is just a parameter name, could also be Base.

Behind the arrow there is the class-signature, without a class name, and here the SuperClass parameter is used after class extends to actually dynamically(!) extend the class given as parameter. What follows is a normal ES6 class-body. Result is a mixin factory of name A that requires a super-class. When you call that factory via A(), you get back a concrete class.

It is the expression class AB extends A(B(Object)) that "mixes" the super-classes A and B together into AB. Because for "mixable classes" it is not possible to receive no super-class, we have to pass the Object class to the definition of B(Object). Try to remove that parameter. You will get "class heritage SuperClass is not an object or null" or similar. The next example will show how to get around that pitfall.

Finally we instantiate the class AB and call all its methods. The console output should be visible above.

This example changes the way how a mixin factory is written.
First it removes the unneeded parentheses around the arrow-class.
Second it introduces a default parameter value (SuperClass = Object) in case no super-class was given by a caller. This adds a little waste to the mixin definition, but relieves callers from the innermost Object parameter (see "simpleSample").

This is the obligatory instanceof test. It should prove that the object ab is of class A, B and AB. Testing against A and B showed TypeError: 'prototype' property of A is not an object. Just the test against class AB worked correctly. Explanation is that the mixable factories A and B need to be called to deliver classes. Only A() gives you a class, A is just the factory!

Thus all super-classes have to be stored somewhere after retrieving them from their factory. This is done in BClass and AClass. They are the concrete super-classes of AB. Testing ab instanceof A() yields false, because the factory creates a new class on any call!

Mind that using instanceof is an anti-pattern, because implementations should not speculate with properties or behaviors of classes.

Constructors are present, including default values for their parameter. The question rises which default value for firstName ("a" or "b") will prevail in the mixed class AB. It turns out that the first wins, in this case class A, being the leftmost in the inheritance chain of class AB extends A(B()). Proven by output "sayA(): A firstName = a".

Mind that all mixin constructors must call super() in some way. This is because they always derive a given class, and ES6 requires the super() call in that case. Of course they can't know which parameters their dynamically given super-class will require, it is the responsibility of the developer that nothing goes wrong here. (In other words, also this kind of multiple inheritance works just under certain circumstances!)

Finally we change the value of the instance field firstName to "xy". ES6 class fields are always public and mutable (like it was in JavaScript). The output shows that we have just one instance-field firstName in class AB, and now its value is "xy".

This is about overriding in multiple inheritance, the critical topic. For simplicity I removed all constructors, they can not be overridden.

Class A overrides the sayB() method of B. Also B tries to override the sayA() method of A, but doesn't succeed because it is the last in inheritance chain class AB extends A (B ()). This bug would uncover just at runtime. In case B.sayA() was not overridden and it would call super.sayA(), a runtime error would be thrown.

That means overrides depend on the inheritance-chain order: the first wins, the last is not allowed to make super-calls in overrides. Again it is the responsibility of the developer to avoid the latter.

The expression this.constructor.name gives the class-name. All of the mixed-in classes A and B state AB as their class name, which is what we would expect.

What has to be proven is that also symbol properties make it into the sub-class. That means, when a super-class retrieved from mixin factory A has a symbol property, that property must be available also in sub-class AB.

Symbol-properties always have to be accessed using [ square brackets ]. They must be defined outside of the class. Mind that also symbol-properties are public and writable, the only way to hide them is to hide the symbol key, but even this is not safe against Object.getOwnPropertySymbols(object).

Montag, 8. Januar 2018

ES6 Export Import Ways

This Blog is about the (too) many possibilities that ES6 gives us for exporting and importing fields, functions and classes.

I just read some articles about how we should export and import, and now I'm puzzled and confused. The technical backgrounds about why some export/import pattern is good or bad stay in the dark. Or what would you suppose to be behind a sentence like "You lose some ES6 module benefits such as tree-shaking and faster access to imports" - ? All I can give by now is a quick overview.

General

Both ES6 exports and imports are possible just on ground-level, they can not be inside some function or code-block.

Imports moreover are "hoisted", that means they get resolved before the source-code is executed where they occur.

Export

Exports are some kind of access modifiers. They state which field, function or class are publicly available for importers.

Named Inline Exports

Simply put the export keyword before the field, function or class to make it public.

./modules/io/output.js
/* publics */
export const ERROR = 0
export const SUCCESS = 1

export function display(text, code) {
    message(text, code === ERROR ? "error" : "success")
}

/* privates */
function message(text, elementId) {
    const outputElement = document.getElementById(elementId)
    outputElement.innerHTML += text+"<br>"
}
./main.js
import { display, SUCCESS, ERROR } from "./modules/io/output.js"

display("Hello Success", SUCCESS)
display("Goodbye ERROR", ERROR)

Advantages:

  • You clearly state which parts of the module are are public
  • Short, simple, concise, the exported names are not duplicated in a separate export clause

Disadvantages:

  • On the import side you need { curly braces }, even when importing just one expression

Separate Export Clause

The export keyword is used as the opener for a list of names that should be exported.

./modules/io/output.js
/* publics */
const ERROR = 0
const SUCCESS = 1

function display(text, code) {
    message(text, code === ERROR ? "error" : "success")
}

export { ERROR, SUCCESS, display }

/* privates */
function message(text, elementId) {
    const outputElement = document.getElementById(elementId)
    outputElement.innerHTML += text+"<br>"
}

The according import is the same as with named inline exports.

Disadvantages:

  • Duplication of possibly lots of names. This seems to be a compatibility-pattern for the now obsolete revealing module pattern.

Export Renaming

./modules/io/info.js
function displayInfo(text) {
    alert(text)
}

export { displayInfo as info }
./main.js
import { info } from "./modules/io/info.js"

info("Hello Info")

Disadvantages:

  • Can create confusion, and is not needed at all. Renaming on the import side is what we will need (in case two imported functions have the same name)

One Default Export per Module

You add default after the export keyword. You can combine the default-export with any number of named inline exports.

./modules/io/info.js
export default function displayInfo(text) {
    const outputElement = document.getElementById("info")
    outputElement.innerHTML += text+"<br>"
}
./main.js
import displayInfo from "./modules/io/info.js"

displayInfo("Hello Info")

Advantages:

  • The import doesn't need the enclosing { curly braces }

Disadvantages:

  • You can have just one default-export per module.
Export Rules of Thumb:
  • Big modules: no default export, just named inline exports.
    Although we should avoid big modules, it's inevitable sometimes, e.g. you want to make a constant for each keyboard key.

  • Small modules: make it even smaller until one default export is sufficient.

Import

Other than export, which plays the role of an access modifier, import is a dependency statement. The ES6 interpreter searches for imports before executing the source code in a module it loads. When it can't resolve all dependencies, or one has a syntax-error, it doesn't execute the loading module either.

ES6 seems to give three options for resolving imported files:

  1. path relative to the location of the importer, always starting with "./", that's what I used in all examples of this article
  2. absolute path (I wouldn't use that unless you want to bind your library-structure to the file-system of your computer)
  3. module names, has to be configured, not specified how, maybe the best way but not yet ready

Named Import

Here I refer to the exports of output.js module presented at the beginning of this article.

./main.js
import { display, SUCCESS, ERROR } from "./modules/io/output.js"

display("Hello Success", SUCCESS)
display("Goodbye ERROR", ERROR)

Advantages:

  • You clearly state what you depend on, not more or less, it is a precise dependency statement, and it is easy to understand

Disadvantages:

  • The import needs the enclosing { curly braces }

Wildcard Import onto Module-Object

The star (asterisk, *) is called "wildcard". It denotes all exports.

./main.js
import * as output from "./modules/io/output.js"

output.display("Hello Success!", output.SUCCESS)

This import-statement creates a new object output that contains all exports of the imported module. You can use them by dereferencing the module object, e.g. output.SUCCESS.

The "as modulename" clause is required! You could say modulename is a "namespace".

Advantages:

  • No duplication of names in the import statement
  • Easy to find out from which module some name comes from, because it is always module.name
  • Ambiguities that must be fixed via "as xxx" (aliasing) will not occur

Disadvantages:

  • You possibly import things that are not needed, your dependency statement is not precise enough and may handycap refactoring
  • Necessity to always use "modulename." as prefix for imported things

Import Renaming

To resolve ambiguity when importing the same name from different modules, import provides renaming via the "as" keyword.

./main.js
import { display as displayOnConsole } from "./modules/io/console.js"
import { display as displayOnPage } from "./modules/io/page.js"

displayOnConsole("Hello Console!")
displayOnPage("Hello Page!")

You wouldn't need that when importing by wildcard, then each of the functions would be bound to the imported module-object.

Advantages:

  • This is needed to fix ambiguities of named imports

Disadvantages:

  • Naming magic, possibly confusion about what the real name of the function is
Import Rules of Thumb:
  • Try to be as explicit as possible.
    Avoid wildcard imports unless it is needed, like e.g. for importing constants for all keyboard keys.

  • Avoid renaming functions and classes whenever possible, especially avoid abbreviations.
    You just create confusing source code.

Resume

This article is not complete. For example I did not discuss anonymous default exports, or re-exporting, because I consider them to be dispensable features.

The question rises whether it makes sense to provide so many different export/import ways in a programming language that claims to become a standard, running in millions of web-browsers. Standards live from being understood by a maximum number of people, thus they need to be unambiguous and simple, a base for communication.

Nevertheless this is a big step forward. Finally JavaScript got dependency management, built into browsers!
Please refer to the many web-pages about ES6 modules for deeper information about how to export and import.