From ad4849db72071e327afee989875e1c53f9e736e6 Mon Sep 17 00:00:00 2001 From: Stijn de Witt Date: Fri, 30 Jun 2017 17:28:37 +0200 Subject: [PATCH 1/2] https://github.com/Download/mics/issues/2 --- src/index.js | 84 +++++++++---------- src/index.spec.js | 203 ++++++++++++++++++++-------------------------- 2 files changed, 128 insertions(+), 159 deletions(-) diff --git a/src/index.js b/src/index.js index 8ba7e0f..077f21d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,12 @@ -var baseclass = class Object{}, - derive = superclass => ({}[superclass.name || 'Object'] = class extends superclass {}) +export { mix, is, like } -export function mix(...args){ - var superclass = !is(args[0]).a('factory') && args.shift() || baseclass - var factory = (is(args[args.length-1]).a('factory') && args.pop()) || derive - superclass = is(superclass).a('mixin') ? superclass.class : derive(superclass) +function mix(...args) { + var superclass = !is(args[0], 'factory') && args.shift() || baseclass + var factory = (is(args[args.length-1], 'factory') && args.pop()) || derive + superclass = is(superclass, 'mixin') ? superclass.class : derive(superclass) if (args.length) factory = (org => superclass => org(args.reduce((s,m) => m.mixin(s), superclass)))(factory) function mixin(superclass) { - var result = is(superclass).a(mixin) ? superclass : factory(superclass) + var result = is(superclass, mixin) ? superclass : factory(superclass) if (mixin.classes.indexOf(result) === -1) mixin.classes.push(result) return result } @@ -25,44 +24,41 @@ export function mix(...args){ }) } -export default mix - -export function is(x, type) { - function a(type) { - if (typeof type == 'string') { - return type == 'class' ? is(x).a('function') && (s => /^class\s/.test(s) || /^.*classCallCheck\(/.test(s.replace(/^[^{]*{\s*/,'').replace(/\s*}[^}]*$/,'')))(x.toString()) : - type == 'mixin' ? is(x).a('function') && !!x.mixin : - type == 'factory' ? is(x).a('function') && !is(x).a('mixin') && !is(x).a('class') && x.length == 1 : - typeof x == type; - } - if (typeof x == 'object') { - if (x instanceof type) return true - if (type.class && x instanceof type.class) return true - if (type.mixin && type.mixin.classes) return type.mixin.classes.reduce((f,c) => f || a(c), false) - } - else if (typeof x == 'function') { - if (x.mixin && x.mixin.mixins.indexOf(type) !== -1) return true - var c = x - while (c !== Object) { - if (c === type || c === type.class) return true - if (type.mixin && type.mixin.classes && type.mixin.classes.indexOf(c) !== -1) return true - c = c.prototype.__proto__.constructor - } +function is(x, type) { + if (typeof type == 'string') { + return type == 'class' ? is(x, 'function') && (s => /^class\s/.test(s) || /^.*classCallCheck\(/.test(s.replace(/^[^{]*{\s*/,'').replace(/\s*}[^}]*$/,'')))(x.toString()) : + type == 'mixin' ? is(x, 'function') && !!x.mixin : + type == 'factory' ? is(x, 'function') && !is(x, 'mixin') && !is(x, 'class') && x.length == 1 : + typeof x == type; + } + if (typeof x == 'object') { + if (x instanceof type) return true + if (type.class && x instanceof type.class) return true + if (type.mixin && type.mixin.classes) return type.mixin.classes.reduce((f,c) => f || is(x,c), false) + } + else if (typeof x == 'function') { + if (x.mixin && x.mixin.mixins.indexOf(type) !== -1) return true + var c = x + while (c !== Object) { + if (c === type || c === type.class) return true + if (type.mixin && type.mixin.classes && type.mixin.classes.indexOf(c) !== -1) return true + c = c.prototype.__proto__.constructor } - return false } + return false +} - function as(type) { - if (a(type)) return true - var itf = type.interface || (is(type, 'function') && getInterface(type.prototype)) - var subject = is(x, 'function') ? x.interface || getInterface(x.prototype) : x - return itf && Object.keys(itf).reduce((f, k) => - f && (is(itf[k], 'function') ? is(subject[k], 'function') : k in subject), true - ) - } +function like(x, type) { + if (is(x, type)) return true + var itf = type.interface || (is(type, 'function') && getInterface(type.prototype)) + var subject = is(x, 'function') ? x.interface || getInterface(x.prototype) : x + return itf && Object.keys(itf).reduce((f, k) => + f && (is(itf[k], 'function') ? is(subject[k], 'function') : k in subject), true + ) +} - var str = x && x.toString() || '' - return type !== undefined ? a(type) : {a, an:a, as} +function getInterface(proto) { + return getPropertyNames(proto).reduce((o,k) => {o[k] = proto[k]; return o}, {}) } function getPropertyNames(proto) { @@ -74,6 +70,6 @@ function getPropertyNames(proto) { return results } -function getInterface(proto) { - return getPropertyNames(proto).reduce((o,k) => {o[k] = proto[k]; return o}, {}) -} +var baseclass = class Object{}, + derive = superclass => ({}[superclass.name || 'Object'] = class extends superclass {}) + diff --git a/src/index.spec.js b/src/index.spec.js index 2ad711f..a28cb85 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -3,7 +3,7 @@ import { expect } from 'chai' import { spy } from 'sinon' import t from 'tcomb' -import { mix, is } from './' +import { mix, is, like } from './' const log = ulog('mics:spec') @@ -28,26 +28,26 @@ describe('mix([superclass] [, ...mixins] [, factory])', function(){ var X = mix(superclass => class X extends superclass {}) var Y = mix(superclass => class X extends superclass {}) var M = mix(X, Y) - expect(is(M).a('mixin')).to.eq(true) + expect(is(M, 'mixin')).to.eq(true) }) it('creates a mix from a superclass', function(){ var C = mix(class Base {}) expect(C).to.be.a('function') - expect(is(C).a('mixin')).to.eq(true) + expect(is(C, 'mixin')).to.eq(true) // special case: class with one-arg constructor looks like a factory class Base {constructor(arg){}} C = mix(Base) expect(C).to.be.a('function') - expect(is(C).a('mixin')).to.eq(true) + expect(is(C, 'mixin')).to.eq(true) expect(new C() instanceof Base).to.eq(true) }) it('creates a mix from a mixed superclass', function(){ var C = mix(class Base {}) - expect(is(C).a('mixin')).to.eq(true) + expect(is(C, 'mixin')).to.eq(true) var D = mix(C) - expect(is(D).a('mixin')).to.eq(true) + expect(is(D, 'mixin')).to.eq(true) expect(new D() instanceof D).to.eq(true) }) @@ -150,17 +150,18 @@ describe('mix([superclass] [, ...mixins] [, factory])', function(){ it('has no side effects on it\'s arguments', function(){ class Test{} - expect(is(Test).a('mixin')).to.eq(false) + expect(is(Test, 'mixin')).to.eq(false) var M = mix(Test) - expect(is(M).a('mixin')).to.eq(true) - expect(is(Test).a('mixin')).to.eq(false) + expect(is(M, 'mixin')).to.eq(true) + expect(is(Test, 'mixin')).to.eq(false) var N = mix(Test, superclass => class N extends superclass {}) - expect(is(N).a('mixin')).to.eq(true) - expect(is(Test).a('mixin')).to.eq(false) + expect(is(N, 'mixin')).to.eq(true) + expect(is(Test, 'mixin')).to.eq(false) }) }) -describe('is(x [, type])', function(){ + +describe('is(x , type)', function(){ it('is a function', function(){ expect(is).to.be.a('function') }) @@ -169,124 +170,96 @@ describe('is(x [, type])', function(){ expect(is.length).to.eq(2) }) - it('when passed two arguments, acts as an alias for is(x).a(type)', function(){ + it('tests whether object `x` implements `type`', function(){ var X = mix(superclass => class X extends superclass {}) var x = new X() - expect(is(x).a(X)).to.eq(true) expect(is(x, X)).to.eq(true) - expect(is(x).a(Date)).to.eq(is(x, Date)) + expect(is(x, Date)).to.eq(false) }) - - it('when passed a single argument, returns an object with sub-methods', function(){ - expect(is({})).to.be.an('object') + it('tests whether class `x` implements `type`', function(){ + var Y = mix(superclass => class Y extends superclass {}) + var X = class X extends mix(Y) {} + expect(is(X, Y)).to.eq(true) }) - - describe('a(type)', function(){ - it('is a function', function(){ - expect(is({}).a).to.be.a('function') - }) - it('tests whether object `x` implements `type`', function(){ - var X = mix(superclass => class X extends superclass {}) - var x = new X() - expect(is(x).a(X)).to.eq(true) - expect(is(x).a(Date)).to.eq(false) - }) - it('tests whether class `x` implements `type`', function(){ - var Y = mix(superclass => class Y extends superclass {}) - var X = class X extends mix(Y) {} - expect(is(X).a(Y)).to.eq(true) - }) - it('tests whether mixin `x` implements `type`', function(){ - var Y = mix(superclass => class Y extends superclass {}) - var X = mix(Y, superclass => class X extends superclass {}) - expect(is(X).a(Y)).to.eq(true) - }) - it('for type == "mixin", tests whether `x` is a mixin', function(){ - expect(is(class X {}).a('mixin')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {})).a('mixin')).to.eq(true) - expect(is({}).a('mixin')).to.eq(false) - expect(is('Hi').a('mixin')).to.eq(false) - expect(is(function(){}).a('mixin')).to.eq(false) - expect(is(function(x){}).a('mixin')).to.eq(false) - expect(is(function(x,y){}).a('mixin')).to.eq(false) - }) - it('for type == "factory", tests whether `x` is a class factory', function(){ - expect(is(class X {}).a('factory')).to.eq(false) - expect(is(mix(class X {})).a('factory')).to.eq(false) - expect(is(class X extends mix(){}).a('factory')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {})).a('factory')).to.eq(false) - expect(is({}).a('factory')).to.eq(false) - expect(is('Hi').a('factory')).to.eq(false) - expect(is(function(){}).a('factory')).to.eq(false) - expect(is(function(x){}).a('factory')).to.eq(true) - expect(is(function(x,y){}).a('factory')).to.eq(false) - }) - it('for type == "function", tests whether `x` is a function', function(){ - expect(is(class X {}).a('function')).to.eq(true) - expect(is(mix(class X {})).a('function')).to.eq(true) - expect(is(class X extends mix(){}).a('function')).to.eq(true) - expect(is(mix(superclass => class Y extends superclass {})).a('function')).to.eq(true) - expect(is({}).a('function')).to.eq(false) - expect(is('Hi').a('function')).to.eq(false) - expect(is(function(){}).a('function')).to.eq(true) - expect(is(function(x){}).a('function')).to.eq(true) - expect(is(function(x,y){}).a('function')).to.eq(true) - }) - it('for type == "object", tests whether `x` is an object', function(){ - expect(is(class X {}).a('object')).to.eq(false) - expect(is(mix(class X {})).a('object')).to.eq(false) - expect(is(class X extends mix(){}).a('object')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {})).a('object')).to.eq(false) - expect(is({}).a('object')).to.eq(true) - expect(is('Hi').a('object')).to.eq(false) - expect(is(function(){}).a('object')).to.eq(false) - expect(is(function(x){}).a('object')).to.eq(false) - expect(is(function(x,y){}).a('object')).to.eq(false) - }) - it('for type == "string", tests whether `x` is a string', function(){ - expect(is(class X {}).a('string')).to.eq(false) - expect(is(mix(class X {})).a('string')).to.eq(false) - expect(is(class X extends mix(){}).a('string')).to.eq(false) - expect(is(mix(superclass => class Y extends superclass {})).a('string')).to.eq(false) - expect(is({}).a('string')).to.eq(false) - expect(is('Hi').a('string')).to.eq(true) - expect(is(function(){}).a('string')).to.eq(false) - expect(is(function(x){}).a('string')).to.eq(false) - expect(is(function(x,y){}).a('string')).to.eq(false) - }) - + it('tests whether mixin `x` implements `type`', function(){ + var Y = mix(superclass => class Y extends superclass {}) + var X = mix(Y, superclass => class X extends superclass {}) + expect(is(X, Y)).to.eq(true) }) - - describe('an(type)', function(){ - it('is a function', function(){ - expect(is({}).a).to.be.a('function') - }) - it('is an alias for `a(type)`', function(){ - var x = is({}) - expect(x.a).to.eq(x.an) - }) + it('for type == "mixin", tests whether `x` is a mixin', function(){ + expect(is(class X {}, 'mixin')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'mixin')).to.eq(true) + expect(is({}, 'mixin')).to.eq(false) + expect(is('Hi', 'mixin')).to.eq(false) + expect(is(function(){}, 'mixin')).to.eq(false) + expect(is(function(x){}, 'mixin')).to.eq(false) + expect(is(function(x,y){}, 'mixin')).to.eq(false) + }) + it('for type == "factory", tests whether `x` is a class factory', function(){ + expect(is(class X {}, 'factory')).to.eq(false) + expect(is(mix(class X {}), 'factory')).to.eq(false) + expect(is(class X extends mix(){}, 'factory')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'factory')).to.eq(false) + expect(is({}, 'factory')).to.eq(false) + expect(is('Hi', 'factory')).to.eq(false) + expect(is(function(){}, 'factory')).to.eq(false) + expect(is(function(x){}, 'factory')).to.eq(true) + expect(is(function(x,y){}, 'factory')).to.eq(false) + }) + it('for type == "function", tests whether `x` is a function', function(){ + expect(is(class X {}, 'function')).to.eq(true) + expect(is(mix(class X {}), 'function')).to.eq(true) + expect(is(class X extends mix(){}, 'function')).to.eq(true) + expect(is(mix(superclass => class Y extends superclass {}), 'function')).to.eq(true) + expect(is({}, 'function')).to.eq(false) + expect(is('Hi', 'function')).to.eq(false) + expect(is(function(){}, 'function')).to.eq(true) + expect(is(function(x){}, 'function')).to.eq(true) + expect(is(function(x,y){}, 'function')).to.eq(true) + }) + it('for type == "object", tests whether `x` is an object', function(){ + expect(is(class X {}, 'object')).to.eq(false) + expect(is(mix(class X {}), 'object')).to.eq(false) + expect(is(class X extends mix(){}, 'object')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'object')).to.eq(false) + expect(is({}, 'object')).to.eq(true) + expect(is('Hi', 'object')).to.eq(false) + expect(is(function(){}, 'object')).to.eq(false) + expect(is(function(x){}, 'object')).to.eq(false) + expect(is(function(x,y){}, 'object')).to.eq(false) + }) + it('for type == "string", tests whether `x` is a string', function(){ + expect(is(class X {}, 'string')).to.eq(false) + expect(is(mix(class X {}), 'string')).to.eq(false) + expect(is(class X extends mix(){}, 'string')).to.eq(false) + expect(is(mix(superclass => class Y extends superclass {}), 'string')).to.eq(false) + expect(is({}, 'string')).to.eq(false) + expect(is('Hi', 'string')).to.eq(true) + expect(is(function(){}, 'string')).to.eq(false) + expect(is(function(x){}, 'string')).to.eq(false) + expect(is(function(x,y){}, 'string')).to.eq(false) }) - describe('as(type)', function(){ + describe('like(type)', function(){ it('is a function', function(){ - expect(is({}).as).to.be.a('function') + expect(like).to.be.a('function') }) it('tests whether `x` can be treated as `type` (has the same interface)', function(){ var Looker = mix(superclass => class Looker extends superclass { look(){} }) - expect(is('Hi').as(Looker)).to.eq(false) - expect(is(8).as(Looker)).to.eq(false) - expect(is({}).as(Looker)).to.eq(false) - expect(is(new Looker()).as(Looker)).to.eq(true) - expect(is({look(){}}).as(Looker)).to.eq(true) - expect(is({walk(){}}).as(Looker)).to.eq(false) + expect(like('Hi', Looker)).to.eq(false) + expect(like(8, Looker)).to.eq(false) + expect(like({}, Looker)).to.eq(false) + expect(like(new Looker(), Looker)).to.eq(true) + expect(like({look(){}}, Looker)).to.eq(true) + expect(like({walk(){}}, Looker)).to.eq(false) class Base {look(){}} - expect(is(Base).as(Looker)).to.eq(true) - expect(is(new Base()).as(Looker)).to.eq(true) + expect(like(Base, Looker)).to.eq(true) + expect(like(new Base(), Looker)).to.eq(true) class Derived extends Base {} - expect(is(Derived).as(Looker)).to.eq(true) - expect(is(new Derived()).as(Looker)).to.eq(true) + expect(like(Derived, Looker)).to.eq(true) + expect(like(new Derived(), Looker)).to.eq(true) }) it('allows mixins to be used as interfaces', (done) => { @@ -300,7 +273,7 @@ describe('is(x [, type])', function(){ } } var promise = new MyPromise() - expect(is(promise).as(Thenable)).to.eq(true) + expect(like(promise, Thenable)).to.eq(true) Promise.resolve(promise).then((result) => { expect(result).to.eq(expected) done() From c62f9f3f78215c5188382548bb5aee9dd5293407 Mon Sep 17 00:00:00 2001 From: Stijn de Witt Date: Fri, 30 Jun 2017 19:31:09 +0200 Subject: [PATCH 2/2] Updated the docs to reflect changes in the API. --- README.md | 195 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 100 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 47adc2f..1c97447 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mics 0.5.0 +# mics 0.6.0 ### Multiple Inheritance Class System **Intuitive mixins for ES6 classes** @@ -12,11 +12,12 @@ *Grady Booch* ## What is it -**mics** *(pronounce: mix)* is a library that makes multiple inheritance in Javascript a -breeze. Inspired by the excellent blog post ["Real" Mixins with Javascript Classes](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/) -by Justin Fagnani, **mics** tries to build a minimal library around the concept of using class expressions (factories) -as mixins. **mics** extends the concepts presented in the blog post by making the mixins first-class citizens -that can be directly used to instantiate objects and can be mixed in with other mixins instead of just with classes. +**mics** *(pronounce: mix)* is a library that makes multiple inheritance in Javascript a breeze. +Inspired by the excellent blog post ["Real" Mixins with Javascript Classes](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/) +by Justin Fagnani, **mics** tries to build a minimal library around the concept of using class +expressions (factories) as mixins. **mics** extends the concepts presented in the blog post by +making the mixins first-class citizens that can be directly used to instantiate objects and can +be mixed in with other mixins instead of just with classes. ## Install with NPM @@ -25,21 +26,22 @@ npm install --save mics ``` ## Direct download -* [mics.umd.js](https://cdn.rawgit.com/download/mics/0.5.0/dist/mics.umd.js) (universal module works in browser and node) -* [mics.min.js](https://cdn.rawgit.com/download/mics/0.5.0/dist/mics.min.js) (minified version of universal module file) +* [mics.umd.js](https://cdn.rawgit.com/download/mics/0.6.0/dist/mics.umd.js) (universal module works in browser and node) +* [mics.min.js](https://cdn.rawgit.com/download/mics/0.6.0/dist/mics.min.js) (minified version of universal module file) ## Include in your app ### import ```js -import { mix, is } from 'mics' +import { mix, is, like } from 'mics' ``` ### require ```js var mix = require('mics').mix var is = require('mics').is +var like = require('mics').like ``` ### AMD @@ -47,25 +49,27 @@ var is = require('mics').is define(['mics'], function(mics){ var mix = mics.mix var is = mics.is + var like = mics.like }); ``` ### Script tag ```html - + ``` ## Usage ### Creating a mixin -Mixins are like classes on steroids. They look and feel a lot like ES6 classes, but they have some additional -capabilities that ES6 classes do not have: +Mixins are like classes on steroids. They look and feel a lot like ES6 classes, +but they have some additional capabilities that ES6 classes do not have: * They can 'extend' from multiple other mixins including (at most one) ES6 class * They have an explicit `interface` which can be inspected and tested at runtime -* They *have* an ES6 `class` that is used to create instances +* They *have* an ES6 `class` that is used to create instances * They have a `mixin` function that mixes in their class body into another type. * They can be invoked without `new` to create new instances @@ -74,13 +78,13 @@ capabilities that ES6 classes do not have: You create mixins with the `mix` function. #### mix([superclass] [, ...mixins] [, factory]) -`mix` accepts an optional *superclass* as the first argument, then a bunch of *mixin*s and an optional -*class factory* as the last argument and returns a mixin. +`mix` accepts an optional *superclass* as the first argument, then a bunch of *mixin*s +and an optional *class factory* as the last argument and returns a mixin. Mostly, you will be using `mix` with a factory to create mixins, like this: ```js -import { mix, is } from 'mics' +import { mix, is, like } from 'mics' var Looker = mix(superclass => class Looker extends superclass { constructor() { @@ -93,16 +97,18 @@ var Looker = mix(superclass => class Looker extends superclass { }) ``` -Notice that the argument to `mix` is an arrow function that accepts a superclass and returns a class -that extends the given superclass. The body of the mixin is defined in the returned class. We call this -a *class factory*. +Notice that the argument to `mix` is an arrow function that accepts a superclass and +returns a class that extends the given superclass. The body of the mixin is defined in +the returned class. We call this a *class factory*. > **Class factory**: An arrow function that accepts a `superclass` and returns a `class extends superclass`. -The `mix` function creates a mixing function based on the given mixins and the class factory and invokes it -with the given superclass to create the ES6 class backing the mixin. It then creates an ES5 constructor function -that uses the ES6 class to create and return new instances of the mixin. Finally it constructs the mixin's interface from the class prototype and attaches the `mixin` function, the `class` and the `interface` to the ES5 constructor -function, creating what in the context of **mics** we call a mixin. +The `mix` function creates a mixing function based on the given mixins and the class +factory and invokes it with the given superclass to create the ES6 class backing the mixin. +It then creates an ES5 constructor function that uses the ES6 class to create and return +new instances of the mixin. Finally it constructs the mixin's interface from the class +prototype and attaches the `mixin` function, the `class` and the `interface` to the ES5 +constructor function, creating what in the context of **mics** we call a mixin. ### Creating instances of mixins We can directly use the created mixin to create instances, because it is just a constructor function: @@ -111,7 +117,9 @@ We can directly use the created mixin to create instances, because it is just a var looker = new Looker() // > A looker is born! looker.look() // > Looking good! looker instanceof Looker // true -typeof looker.mixin // function +typeof looker.mixin // 'function' +typeof looker.class // 'function' +typeof looker.interface // 'object' ``` And because it's an ES5 constructor function, we are allowed to invoke it without `new`: @@ -121,11 +129,13 @@ var looker = Looker() // > A looker is born! looker.look() // > Looking good! ``` -> ES6 made newless invocation of constructors throw an error for ES6 classes, because in ES5 it was often a cause -> for bugs when programmers forgot `new` with constructors that assumed `new` was used. However I (with many others) -> believe that not using `new` is actually better for writing maintainable code. So mics makes sure that it's -> constructors work whether you use `new` on them or not, because the backing ES6 class is always invoked with `new` -> as it should be. Whether you want to write `new` or not in your code is up to you. +> ES6 made newless invocation of constructors throw an error for ES6 classes, because +> in ES5 it was often a cause for bugs when programmers forgot `new` with constructors +> that assumed `new` was used. However I (with many others) believe that not using `new` +> is actually better for writing maintainable code. So mics makes sure that it's +> constructors work whether you use `new` on them or not, because the backing ES6 class +> is always invoked with `new` as it should be. Whether you want to write `new` or not +> in your code is up to you. ### Mixing multiple mixins into a new mixin Let us define mixins `Walker` and `Talker` to supplement our `Looker`: @@ -158,70 +168,63 @@ var donald = Duck() donald.talk() // > Quack, quack, quack (Duckian for "Blah, blah, blah") ``` -As you can see, we can override methods and use `super` to call the superclass method, just like -we can with normal ES6 classes. +As you can see, we can override methods and use `super` to call the superclass method, +just like we can with normal ES6 classes. ### Testing if an object is (like) a mixin or class -`instanceof` works for mixin instances like it does for ES6 classes. But, like ES6 classes, it does not -support multiple inheritance. In the example above, `Looker` is effectively the superclass for `Duck`. -`Walker` and `Talker` are mixed into `Duck` by dynamically creating *new* classes and injecting them into -the inheritance chain between `Looker` and `Duck`. Because these are *new* classes, instances of them are +`instanceof` works for mixin instances like it does for ES6 classes. But, like ES6 +classes, it does not support multiple inheritance. In the example above, `Looker` +is effectively the superclass for `Duck`. `Walker` and `Talker` are mixed into `Duck` +by dynamically creating *new* classes and injecting them into the inheritance chain +between `Looker` and `Duck`. Because these are *new* classes, instances of them are not recognized by `instanceof` as instances of `Walker` and `Talker`. Fortunately, **mics** gives us an `is` function, which does understand multiple inheritance. -#### is(subject [, type]) -The first parameter to `is` is required and defines the subject to test. This can be an instance or a type. -The second parameter is optional. If specified, `is` calls `a` (see below) and returns a boolean. If not specified, `is` returns an object that has submethods `a`/`an` and `as`. - -#### a(type) -Tests whether the subject is-a `type`, or, when the subject is itself a type, whether the subject extends from `type`. +#### is(subject, type) +Tests whether `subject` is-a `type` or extends from `type`. +The first parameter to `is` defines the subject to test. This can be an instance or +a type. The second parameter is either a type (constructor function, ES6 class or mixin) +or a type string. ```js duck instanceof Duck // true duck instanceof Looker // true, but: duck instanceof Walker // false! mix created a *new class* based on the factory -// is..a to the rescue! -is(duck).a(Walker) // true +// `is` to the rescue! +is(duck, Walker) // true // we can also test the type -is(Duck).a(Walker) // true -is(Talker).a(Walker) // false -``` - -#### an(type) -For fluidity, `an` is an alias of `a`: - -```js -is(animal).an(Elephant) -is(animal).a(Lion) +is(Duck, Walker) // true +is(Talker, Walker) // false ``` -#### as(type) -Often, we don't really care whether the object *is* a certain type, we just want to know whether -we can treat it *as* a certain type. Use `is(subject).as(type)` to test whether a subject adheres -to the same interface as is defined by `type`: +#### like(type) +Often, we don't really care whether the object *is* a certain type, we just want to know +whether we can treat it *like* a certain type. Use `like(subject, type)` to test whether +a subject adheres to the same interface as is defined by `type`: ```js var viewer = { // create an object with the look(){} // same interface as Looker -} -is(viewer).a(Looker) // false, but -is(viewer).as(Looker) // true +} +is(viewer, Looker) // false, but +like(viewer, Looker) // true ``` -A good example of how this might be useful can be found in the new ES6 feature Promises. Here we have -the concept of a 'thenable'. This is any object that has a `then` method on it. Methods in the Promise -API often accept thenables instead of promise instances. Have a look at `Promise.resolve` for example: +A good example of how this might be useful can be found in the new ES6 feature Promises. +Here we have the concept of a 'thenable'. This is any object that has a `then` method on +it. Methods in the Promise API often accept thenables instead of promise instances. Have +a look at `Promise.resolve` for example: > Promise.resolve(value) -> Returns a Promise object that is resolved with the given value. If the value is a thenable (i.e. -> has a then method), the returned promise will "follow" that thenable, adopting its eventual state; -> otherwise the returned promise will be fulfilled with the value. +> Returns a Promise object that is resolved with the given value. If the value is a +> thenable (i.e. has a `then` method), the returned promise will "follow" that thenable, +> adopting its eventual state; otherwise the returned promise will be fulfilled with the value. [mdn](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/Promise) -Using `mix` to define an interface and `is..as` to test for it, we can very naturally express the -concept of a thenable from the Promise spec in code: +Using `mix` to define an interface and `like` to test for it, we can very naturally +express the concept of a thenable from the Promise spec in code: ```js /** Defines a Thenable */ @@ -234,11 +237,11 @@ var MyPromise = mix(superclass => class MyPromise extends superclass { resolve('Hello, World!') } } -// We can check whether the class is thenable using is..as -is(MyPromise).as(Thenable) // true +// We can check whether the class is thenable using like +like(MyPromise, Thenable) // true // we can also check instances var promise = new MyPromise() -is(promise).as(Thenable) // true +like(promise, Thenable) // true // Ok, that means we can use Promise.resolve! Promise.resolve(promise).then((result) => { console.info(result) // > 'Hello, World!' @@ -246,9 +249,10 @@ Promise.resolve(promise).then((result) => { ``` ### Using a custom ES5 constructor -The default constructor returned from `mix` is a one-liner that invokes the ES6 class with `new`. But there -could be reasons to use a different function instead. `mix` allows you to supply a custom constructor to be -used instead. You do this by providing a `static constructor` in the class body: +The default constructor returned from `mix` is a one-liner that invokes the ES6 +class with `new`. But there could be reasons to use a different function instead. +`mix` allows you to supply a custom constructor to be used instead. You do this +by providing a `static constructor` in the class body: ```js var Custom = mix(superclass => class Custom extends superclass{ @@ -263,7 +267,7 @@ is(test).a(Custom) // true ``` ### Bonus -As a bonus, you can use `is(..).a(..)` to do some simple type tests by passing a +As a bonus, you can use `is()` to do some simple type tests by passing a string for the type: ```js @@ -272,28 +276,29 @@ var factory = superclass => class Y extends superclass {} var Y = mix(factory) var Z = mix(X, Y) -is(X).a('function') // true -is(X).a('class') // true -is(X).a('mixin') // false -is(X).a('factory') // false - -is(factory).a('function') // true -is(factory).a('class') // false -is(factory).a('mixin') // false -is(factory).a('factory') // true - -is(Y).a('function') // true -is(Y).a('class') // false -is(Y).a('mixin') // true -is(Y).a('factory') // false - -is(Z).a('function') // true -is(Z).a('class') // false -is(Z).a('mixin') // true -is(Z).a('factory') // false +is(X, 'function') // true +is(X, 'class') // true +is(X, 'mixin') // false +is(X, 'factory') // false + +is(factory, 'function') // true +is(factory, 'class') // false +is(factory, 'mixin') // false +is(factory, 'factory') // true + +is(Y, 'function') // true +is(Y, 'class') // false +is(Y, 'mixin') // true +is(Y, 'factory') // false + +is(Z, 'function') // true +is(Z, 'class') // false +is(Z, 'mixin') // true +is(Z, 'factory') // false ``` -Supported type strings: `"class"`, `"mixin"`, `"factory"`, and any type strings that can be passed to `typeof`. +Supported type strings: `"class"`, `"mixin"`, `"factory"`, and any type strings +that can be passed to `typeof`. * class: x is a (possibly Babel-transpiled) ES6 class * mixin: x is a mixin that is the result of calling `mix` * factory: x is a class factory function