the Type module ties together a few ideas to make programming with types easier.
Type.check
is a core function, Type.check(v, t)
will
return true
if the value v
is of the type t
.
It can be used as a simple replacement for the instanceof
operator,
where you might say a instanceof c
, Type.check(a, c)
will give
the same answer (but slower).
It also treats native types (string, number, boolean, ...), specially so that the corresponding classes are synonyms for the native type.
This is so that both Type.check("", String)
and Type.check(new String(), String)
will be true.
It also accomodates for some of the weirder javascript "types" like NaN
or arguments
.
The Type
api for common type checking operations is much more regular and hides some
of the weird dark corners of Javascript.
v instanceof Constructor == Type.check(v, Constructor)
typeof v === "string" == Type.check(v, String)
Object(v) instanceof String == Type.check(v, String)
v == null == Type.check(v, null) == Type.check(v, Type.Null)
isNaN(v) == Type.check(v, NaN) == Type.check(v, Type.NAN)
typeof v === "arguments" == Type.check(v, Type.Arguments)
// Note: for brevity you can cache the `Type.check` and call it separately e.g.
var check = Type.check;
check("test", String); // true
check(null, Object); // false
Types are values rather than just an idea in the head of the developer. Which makes writing code about them much easier.
function assert(value, type) {
if (!Type.check(value, type)) {
throw TypeError();
}
}
function add(a, b) {
assert(a, Number);
assert(b, Number);
return a + b;
}
Type
also provides more flexible types, like union types.
A Type.Union
type is a collection of subtypes. A given value v
,
is of Union type u
if it is of any of u
's subtypes.
var Ordered = new Type.Union(String, Array, Type.Arguments);
Type.check('test', Ordered); // true
Type.check([1, 2], Ordered); // true
Type.check({a: 1}, Ordered); // false
Type.check(null, Ordered); // false
Duck-typing is the concept that if an object supports the methods I care about, then I don't care about anything else.
When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.
-- James Whitcomb Riley 'Duck Typing' on wikipedia
A Type.Duck
type demands that an object have certain properties
with certain types.
var Collection = new Type.Duck({
length: Number,
item: Function
});
Type.check(document.getElementsByTagName("a"), Collection); // true
Sometimes, types don't fit into a neat hierachy. When all else
fails, just use Type.Predicate
.
var Palindrome = new Type.Predicate(function (str) {
var str_ = str.replace(/[^a-z]/g, '').toLowerCase();
return str_ == str_.split('').reverse().join('');
});
Test.check("Rats live on no evil star", Palindrome); // true
Test.check("She sells seashells by the seashore", Palindrome); // false
Type.Duck
is in fact a subclass of Type.Predicate
.
A specialized type adds an additional restriction to an existing type.
var Integer = new Specialized(Number, function (n) {
return parseInt(n, 10) === n;
});
Type.check(1, Integer); // true
Type.check(1.5, Integer); // false
Multiple Dispatch on Wikipedia
Type.Generic
is a class of function that can be specialized for certain
types of arguments.
var reverse = new Type.Generic();
reverse.defineMethods(
[ Array ], function (arr) {
return arr.reverse();
},
[ String ], function (str) {
return str.split('').reverse().join('');
}
);
reverse("Hello World!"); // "!dlroW olleH"
reverse([1, 2, 3, 4, 5]); // [5, 4, 3, 2, 1]
You can use Generics to add functionality to types without modifying an existing Class's prototype.
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
You can also dispatch against types which can't have traditional javascript methods, like null.
reverse.defineMethod([ null ], function (n) {
return null;
});
reverse(null); // null
reverse(false); // FAIL~!
Type.Signature
wraps an existing function, checking the arguments on the way in and checking
the return value on the way out.
var sum = new Type.Signature(Number, Number, Number).wrap(
function (a, b) {
return a + b;
});
sum(2, 4)
Type.Interface is intended to be similar to a Haskell TypeClass in that it is a collection of Generics that are known to be implemented for various types
In this example, I create an interface Seq
, and create an implementation for javascript
Array
s and also use defineClass
to define a class Stream
that implements the interface.
Now I can define the generic reduce
that has the signature Function, Seq, Any -> Any
.
var Seq = new Type.Interface({
first: [Seq, Type.Any],
rest: [Seq, Seq],
nth: [Seq, Number, Type.Any],
isEmpty: [Seq, Boolean],
length: [Seq, Number]
});
Seq.implement(Array, {
first: function (seq) {
return seq[0];
},
rest: function (seq) {
return seq.slice(1);
},
nth: function (seq, n) {
return seq[n];
},
isEmpty: function (seq) {
return seq.length === 0;
},
length: function (seq) {
return seq.length;
}
});
var Stream = Type.defineClass({
Implements: [ Seq ],
initialize: function (first, rest) {
this._first = first;
this._rest = rest;
},
first: function () {
return this._first;
},
rest: function () {
return this._rest();
},
isEmpty: function () {
return false;
},
nth: function (n) {
var ls = this;
while (n-- > 0) {
ls = ls.rest();
}
return ls.first() ;
},
length: function () {
var len = 0,
ls = this;
while (!ls.isEmpty()) {
len++;
ls = ls.rest();
}
return len;
}
});
var EmptyStream = Type.defineClass({
Extends: Stream,
first: function () { throw new Error(); },
rest: function () { throw new Error(); },
nth: function (n) { throw new Error(); },
isEmpty: function () { return true; },
length: function () { return 0; }
});
var reduce = new Type.Generic();
reduce.defineMethod([Function, Seq, Type.Any], function (fn, ls, init) {
while (!Seq.generics.isEmpty(ls)) {
init = fn(Seq.generics.first(ls), init);
ls = Seq.generics.rest(ls);
}
return init;
});
var testStream = new Stream(1, function () {
return new Stream(2, function () {
return new EmptyStream();
});
});
var testArray = [1, 2, 3, 4, 5];
equals(reduce(function (a, b) { return a + b; }, testStream, 0), 3);
equals(reduce(function (a, b) { return a + b; }, testArray, 0), 15);