Skip to content

Mixin language support (with toy implementation) #2919

Closed
@dbarbeau

Description

@dbarbeau

Hello TypeScripters.

I might be opening a can of worms which might bring an age of darkness upon us (but see PS). Anyway, I've done an attempt to add language support for mixins.

For the user, it is similar to the extends or implements keywords, and thus goes by mixes. The semantic intent of the keyword is that a Mixture is NOT a derived class of its Mixins, it just copies their static and dynamic properties.

Several mixins can be supplied, the order determines which property will finally be mixed (newer definitions shadow previous definitions).

What this does is roughly equivalent to http://www.typescriptlang.org/Handbook#mixins, except there is no need to copy attributes manually, so mixins become more manageable - hopefully.

class Base {
    say(hello:string) {
        console.log(hello);
    }
}

class Mixin1 {
    something : string = "Couac couac";

    fn1() {
            console.log(this.something);
    }
}


class Mixin2 {      
    something : string = "shadowing you, little duck!";  

    fn2() {
            console.log("The dragon is", this.something);
    }
}

/* Only mixes one mixin */
class SimpleMixture mixes Mixin1 {

}

/* Mixes several mixins, property definitions in newer mixins (from left
to right) shadow those from previous mixins AND those in the mixture
class itself, including inherited ones. */
class MultiMixture extends Base mixes Mixin1, Mixin2 {

}


var sm1 = new SimpleMixture();
sm1.fn1();

var sm2 = new MultiMixture();
sm2.fn1();
sm2.fn2();

console.log( sm1 instanceof Base );
console.log( sm1 instanceof Mixin1 );
console.log( sm2 instanceof Base );
console.log( sm2 instanceof Mixin2 );

Which emits

var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var __applyMixins = this.__applyMixins || function (mixtureTarget) {
    for(var a=1; a<arguments.length; ++a) {
        var mixin = arguments[a];
        Object.getOwnPropertyNames(mixin.prototype).forEach( function(name) {
            mixtureTarget.prototype[name] = mixin.prototype[name];
        })
    }; 
};
var Base = (function () {
    function Base() {
    }
    Base.prototype.say = function (hello) {
        console.log(hello);
    };
    return Base;
})();
var Mixin1 = (function () {
    function Mixin1() {
        this.something = "Couac couac";
    }
    Mixin1.prototype.fn1 = function () {
        console.log(this.something);
    };
    return Mixin1;
})();
var Mixin2 = (function () {
    function Mixin2() {
        this.something = "shadowing you, little duck!";
    }
    Mixin2.prototype.fn2 = function () {
        console.log("The dragon is", this.something);
    };
    return Mixin2;
})();
/* Only mixes one mixin */
var SimpleMixture = (function () {
    function SimpleMixture() {
        Mixin1.apply(this) ;
    }
    __applyMixins(/*mixtureTarget*/ SimpleMixture, Mixin1);
    return SimpleMixture;
})();
/* Mixes several mixins, property definitions in newer mixins (from left
to right) shadow those from previous mixins AND those in the mixture
class itself, including inherited ones. */
var MultiMixture = (function (_super) {
    __extends(MultiMixture, _super);
    function MultiMixture() {
        _super.apply(this, arguments);
        Mixin1.apply(this) ;
        Mixin2.apply(this) ;
    }
    __applyMixins(/*mixtureTarget*/ MultiMixture, Mixin1, Mixin2);
    return MultiMixture;
})(Base);
var sm1 = new SimpleMixture();
sm1.fn1();
var sm2 = new MultiMixture();
sm2.fn1();
sm2.fn2();
console.log(sm1 instanceof Base);
console.log(sm1 instanceof Mixin1);
console.log(sm2 instanceof Base);
console.log(sm2 instanceof Mixin2);

and outputs

>>> Couac couac
>>> shadowing you, little duck!
>>> The dragon is shadowing you, little duck!
>>> false
>>> false
>>> true
>>> false

At this point it seems to work with target ES<6 with quite some limitations (the user can't pass arguments to the mixin constructors). Type checking seems to be working (although I could not make Eclipse or Emacs behave with the new keyword). But be warned Very early experimental preview code from a bad JS programmer who knew nothing about TSC's internals three days ago and who was in a rush. Be warned.

I did this to overcome a maintainability issue with my code and rushed this implementation. It was also an exercise to learn more about TSC. I'm also aware that it might not be a great idea to add keywords to a language each time one gets into trouble, but if others see interest in it or you have suggestions, the branch is here https://github.com/dbarbeau/TypeScript/tree/mixes. Just don't expect this implementation to be stable/complete/anything. Actually, if it can just spark some interest/discussion on how to make mixins more useable, then I'm ok with that.

Daniel
PS: Don't get mad at me :)
PS: I'm now testing this with more "serious code", I will quickly see if it is indeed practical.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DiscussionIssues which may not have code impact

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions