1.0.0 • Published 9 years ago

introspect-typed v1.0.0

Weekly downloads
1
License
GPL
Repository
github
Last release
9 years ago

introspect-typed

Provides rudimentary type checking and function overloading in Javascript.

Install

npm install introspect-typed

API

matchType: Type checking individual values

Other methods (typeChecked, overload) are based on this functionality.

Direct match

    var matchType = require('introspect-typed').matchType;
    matchType(String, 'yes!');  // => true
    matchType(Number, 'no...'); // => false

Getting a matcher function

    var matchType = require('introspect-typed').matchType;
    var stringMatcher = matchType(String);
    stringMatcher('yes!');  // => true
    stringMatcher(22.145);  // => false

Custom types work as well

    var matchType = require('introspect-typed').matchType;
    function Custo () { this.type = 'custo'; }
    var custoMatcher = matchType(Custo);
    custoMatcher(new Custo());      // => true
    custoMatcher({type: 'custo'});  // => false

Matching against any type

Use Typed.Any if you want to match any value.

    var Typed = require('introspect-typed');
    var matchType = Typed.matchType;
    matchType(Typed.Any, undefined); // => true

Matching against several types

Use Typed.Either if you want to match several types.

    var Typed = require('introspect-typed');
    var matchType = Typed.matchType;
    matchType(Typed.Either(String, Number), 'string'); // => true
    matchType(Typed.Either(String, Number), 2);        // => true
    matchType(Typed.Either(String, Number), {});       // => false

Matching against a predicate

Use Typed.Matcher if you want to match with a predicate.

    var Typed = require('introspect-typed');
    var matchType = Typed.matchType;
    var type = Typed.Matcher(function (v) { return v.length === 2; });
    matchType(type, {length: 2}); // => true
    matchType(type, '12');        // => true
    matchType(type, [1,2]);       // => true
    matchType(type, '1');         // => false
    matchType(type, [1,2,3]);     // => false

Matching against an ES6 iterable

Iterables are objects with a Symbol.iterator property containing a function. In ES6, strings, arrays, sets, maps and generator functions, among others, are iterables.

Use Typed.Iterable if you want to match es6 iterables.

    var Typed = require('introspect-typed');
    var Iterable = Typed.Iterable;
    matchType(Iterable, []);    // => true
    matchType(Iterable, '12');  // => true
    matchType(Iterable, {});    // => true
    matchType(Iterable, 2);     // => false
    matchType(Iterable, null);  // => false

typeChecked: Type checking function calls

  var Typed = require('introspect-typed');
  var typeChecked = Typed.typeChecked;
  var Either = Typed.Either;
  function Custo () { this.type = 'custo'; }

Declare a type checked function by calling typeChecked with

  • an array of types,
  • the function you want to type check.
  var tcFn = typeChecked([Either(String, Number), Custo], function (s, c) {
    return s + c;
  });
  tcFn('string', new Custo()); // => 'string[object Object]'
  tcFn(Infinity, new Custo()); // => 'Infinity[object Object]'

Rest parameters

Check type for rest parameters by using Typed.Rest. In ES6:

var Rest = Typed.Rest;
var typedFn = typeChecked([String, Number, Rest(Either(Custom, String))],
  (s, n, ...cs) => ({ result: s.repeat(n), rest: cs.length });

In ES5:

  var Rest = Typed.Rest;
  var typedFn = typeChecked([String, Number, Rest(Either(Custom, String))],
    function (s, n) {
      var cs = [].slice.call(arguments, 2);
      return { result: s.repeat(n), rest: cs.length };
    });
  • Rest(String) will match all strings at the end of the arguments.
  • Rest(Any) will match anything at the end of the arguments.
  • Rest(Either(X,Y)) will match any number of Xs and Ys at the end of the arguments.

Note that Rest(...) must be the last element in the type array, otherwise an error will be thrown while matching.

Error management

By default, the typechecked function throws a TypeError when not given proper arguments.

  tcFn('string', {type:'custo'}); // => throws TypeError

You can set the error management behaviour yourself (at any time).

  tcFn.onError(function (error, args, types) {
    console.error(error);
  });

And set back the original throwing behaviour by calling onError with a falsey value.

  tcFn.onError(false);

overload: Overloading functions

Chaining methods

When creating an API, we often want our methods to accept different kinds of inputs, but the code that results from these checks is ugly and provides many occasions for bugs.

Instead, using overload, one can do the following:

  var overload = require('introspect-typed').overload;

  var createObj = overload(
    // This method is the default method, called when no other case
    // has matched
    function (obj) {
      obj.test = 1;
      return obj;
    }
  )
  .when('withName', // Here we define a 'withName' method in the overloading object
             [String, Object], // which matches calls with a String and an Object
    function (name,   obj,     o) // the last arg is the overloading object
    {
      obj.name = name;
      // We can use the overloading object to call other methods defined
      // for example the default method, passed to overload
      return createObj.default(obj);
    }
  )

  .when([String, String, Object], function (name, desc, obj, o) {
    obj.desc = desc;
    // Or we can use it to call named overloading methods.
    return createObj.withName(name, obj);
  });

Without the comments:

  var overload = require('introspect-typed').overload;

  var createObj = overload(function (obj) {
    obj.test = 1;
    return obj;
  }).when('withName', [String, Object], function (name, obj, o) {
    obj.name = name;
    return createObj.default(obj);
  }).when([String, String, Object], function (name, desc, obj, o) {
    obj.desc = desc;
    return createObj.withName(name, obj);
  });

Calling a named overloaded method directly bypasses the type check.

To have a completely type checked method, one could do this instead:

  var overload = require('introspect-typed').overload;
  var typeChecked = require('introspect-typed').typeChecked;

  var createObj = overload([Object], function (obj) {
    obj.test = 1;
    return obj;
  })
  .when([String, Object], function (name, obj, o) {
    obj.name = name;
    return createObj(obj);
  })
  .when([String, String, Object], function (name, desc, obj, o) {
    obj.desc = desc;
    return createObj(name, obj);
  });

The calls to overload with arguments matching [Array, Function] are equivalent to overload(typeChecked(types, fn)).

Complete overloading

  var overload = require('introspect-typed').overload;

  var fn = overload(function () { return this; })

    // give a number : increments it
    .when([Number],
      function (a) { return a + 1; })

    // give two numbers : multiply them
    .when([Number, Number],
      function (n1, n2) { return n1 * n2; })

    // give two strings: join them with '-'
    .when([String, String],
      function (s1, s2) { return s1 + '-' + s2; })

    // give 3 strings: join them with '!'
    .when([String, String, String],
      function (s1, s2, s3) { return [s1,s2,s3].join('!'); })

    // returns the length for a String or an Array
    .when([Either(String,Array)],
      function (v) { return v.length; })

    // logs arguments when there are 4 of them
    .when([Any, Any, Any, Any],
      function () { console.log(arguments); return arguments[3]; })

    // give a number and a string: repeat the string n times
    .when([Number, String],
      function (n, s) { return s.repeat(n); });

Calling this method with different kinds of arguments changes its behaviour.

    expect(fn('2','57', 'x')).to.be.equal('2!57!x');
    expect(fn('2','57')).to.be.equal('2-57');
    expect(fn(2,'57')).to.be.equal('5757');
    expect(fn(2,57)).to.be.equal(2*57);
    expect(fn(42)).to.be.equal(43);

The this value is conserved.

    expect(fn.apply(42, [])).to.be.equal(42);

build

The build function allows to create a new Typed context.

This allows to modify the matchType function, to pass more cases to the type checking.

    var build = require('introspect-typed').build;
    var newTypedContext = build();
    var matchType = newTypedContext.matchType;

    var Truthy = {};

    expect(matchType(Truthy, 1)).to.be.false;
    expect(matchType(Truthy, null)).to.be.false;

    matchType.addTypeMatchCase({
      case: function (type) { return type === Truthy; },
      match: function (type) { return function (val) { return !!val }; }
    });

    expect(matchType(Truthy, 1)).to.be.true;
    expect(matchType(Truthy, null)).to.be.false;