Node
npm install tcomb
var t = require('tcomb');
What's a type? In tcomb a type is represented by a function T
such that:
- has signature
T(value)
where value depends on the nature ofT
- is idempotent, that is
T(T(value)) = T(value)
- owns a static function
T.is(x)
returningtrue
ifx
is an instance ofT
An irreducible type is a type that can't be built with other types. Examples of such types are JavaScript native types:
JavaScript native types
t.String
: strings (deprecated aliast.Str
)t.Number
: numbers (deprecated aliast.Num
)t.Boolean
: booleans (deprecated aliast.Bool
)t.Array
: arrays (deprecated aliast.Arr
)t.Object
: plain objects (deprecated aliast.Obj
)t.Function
: functions (deprecated aliast.Func
)t.Error
: errors (deprecated aliast.Err
)t.RegExp
: regular expressions (deprecated aliast.Re
)t.Date
: dates (deprecated aliast.Dat
)
There are 2 additional irriducible types defined in tcomb:
Additional types
t.Nil
:null
orundefined
t.Any
: any value
Every type defined with tcomb owns a static predicate is(x: any) -> boolean
useful for type checking:
var t = require('tcomb');
t.String.is('a string'); // => true
t.String.is(1); // => false
t.Number.is('a string'); // => false
t.Number.is(1); // => true
// and so on...
tcomb provides a built-in t.assert(guard: boolean, message?: string)
function, if an assert fails a TypeError
is thrown.
assert(t.String.is('a string')); // => ok
assert(t.String.is(1)); // => throws
guard
is a boolean conditionmessage
optional string useful for debugging
var x = -2;
var min = 0;
// throws "-2 should be greater then 0"
assert(x > min, `${x} should be greater then ${min}`);
Another way to ensure the correct type is to use types as constructors:
var s1 = t.String('a string'); // => ok
var s2 = t.String(1); // => throws
tcomb can also be used in existing code to add type safety:
// plain old JavaScript class
function Point (x, y) {
this.x = x;
this.y = y;
}
var p = new Point(1, 'a'); // silent error
// Now with asserts inserted:
function Point (x, y) {
this.x = t.Number(x);
this.y = t.Number(y);
}
var p = new Point(1, 'a'); // => throws
To define your own irreducible types use the t.irreducible(name: string, predicate: (x: any) => boolean)
combinator:
var t = require('tcomb');
var React = require('react');
var ReactElement = t.irreducible('ReactElement', React.isValidElement);
ReactElement.is(<div/>); // => true
Every type owns a meta
object containing the following properties:
kind
: the type kind, equal to"irreducible"
for irreducible typesis
: the predicate
Example: the meta
object of t.String
:
{
kind: 'irreducible',
is: function isString(x) {
return typeof x === 'string';
}
}
Tech note. All the built-in irreducible types are defined with t.irreducible()
.
Meta objects are a distinctive feature of tcomb, allowing runtime type introspection.
Type combinators are the tcomb way to define new composite types from those already defined, that is they combine old types in a new one.
You can refine a type using the refinement(type, predicate, name)
combinator where:
type
is a type already definedpredicate
is a predicatename
is an optional string useful for debugging purposes
Example:
// defines a type representing positive numbers
var Positive = t.refinement(t.Number, (n) => n >= 0, 'Positive');
Positive.is(1); // => true
Positive.is(-1); // => false
Refinements have the following meta
object:
{
kind: 'subtype',
type: type,
predicate: predicate
}
You can define an enum type using the enums(map: Object, name?: string)
combinator where:
map
is a hash whose keys are the enums (values are free)name
is an optional string useful for debugging purposes
var Country = t.enums({
IT: 'Italy',
US: 'United States'
}, 'Country');
Country.is('IT'); // => true
Country.is('FR'); // => false
Enums have the following meta
object:
{
kind: 'enums',
map: map
}
If you don't care of values you can use enums.of(keys, name?)
where:
keys: Array<string|number> | string
is the array of enums or a string where the enums are separated by spacesname: ?string
is an optional string useful for debugging purposes
// values will mirror the keys
var Country = t.enums.of('IT US', 'Country');
// same as
var Country = t.enums.of(['IT', 'US'], 'Country');
// same as
var Country = t.enums({
IT: 'IT',
US: 'US'
}, 'Country');
You can define a struct type using the struct(props, name?)
combinator where:
props
is a hash whose keys are the field names and the values are the fields typesname
is an optional string useful for debugging purposes
var Point = t.struct({
x: t.Number,
y: t.Number
}, 'Point');
// constructor usage, `p` is immutable, new is optional
var p = new Point({x: 1, y: 2});
Point.is(p); // => true
Tech note. Point.is
uses instanceof
internally.
Struct methods are defined as usual:
Point.prototype.toString = function () {
return `(${this.x}, ${this.y})`;
};
Structs have the following meta
object:
{
kind: 'struct',
props: props
}
Every struct constructor owns an extend(mixins, name)
function where:
mixins
can be an object containing the new props, an array of objects, a type or an array of typesname
the name of the new struct
var Point3D = Point.extend({z: t.Number}, 'Point3D');
// multiple inheritance
var A = struct({...});
var B = struct({...});
var MixinC = {...};
var MixinD = {...};
var E = A.extend([B, MixinC, MixinD]);
extend
supports prototypal inheritance:
var Rectangle = t.struct({
width: t.Number,
height: t.Number
});
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};
var Cube = Rectangle.extend({
thickness: t.Number
});
// typeof Cube.prototype.getArea === 'function'
Cube.prototype.getVolume = function () {
return this.getArea() * this.thickness;
};
Note. Repeated props are not allowed:
var Wrong = Point.extend({x: t.Number}); // => throws
You can define a tuple type using the tuple(types, name)
combinator where:
types
is a list of typesname
is an optional string useful for debugging purposes
Instances of tuples are plain old JavaScript arrays.
var Area = t.tuple([t.Number, t.Number]);
// constructor usage, `area` is immutable
var area = Area([1, 2]);
Tuples have the following meta
object:
{
kind: 'tuple',
types: types
}
You can define a list type using the list(type, name)
combinator where:
type
is the type of list itemsname
is an optional string useful for debugging purposes
Instances of lists are plain old JavaScript arrays.
var Path = t.list(Point);
// costructor usage, `path` is immutable
var path = Path([
{x: 0, y: 0}, // tcomb hydrates automatically using the `Point` constructor
{x: 1, y: 1}
]);
Lists have the following meta
object:
{
kind: 'list',
type: type
}
You can define a dictionary type using the dict(domain, codomain, name)
combinator where:
domain
is the type of keyscodomain
is the type of valuesname
: is an optional string useful for debugging purposes
Instances of dicts are plain old JavaScript objects.
var Tel = dict(String, t.Number);
// costructor usage, `tel` is immutable
var tel = Tel({'jack': 4098, 'sape': 4139});
Dicts have the following meta
object:
{
kind: 'dict',
domain: domain,
codomain: codomain
}
You can define a union of types using the union(types, name)
combinator where:
types
is a list of typesname
is an optional string useful for debugging purposes
var ReactKey = t.union([t.String, t.Number]);
ReactKey.is('a'); // => true
ReactKey.is(1); // => true
ReactKey.is(true); // => false
Unions have the following meta
object:
{
kind: 'union',
types: types
}
In order to use a union as a constructor you must implement the static function:
function (x: Any) -> Type
An example implementation for the ReactKey
union:
ReactKey.dispatch = function (x) {
if (t.String.is(x)) return t.String;
if (t.Number.is(x)) return t.Number;
};
// now you can do this without a fail
var key = ReactKey('a');
tcomb provides a default implementation of dispatch
which you can override.
You can define an intersection of types using the intersection(types, name)
combinator where:
types
is a list of typesname
is an optional string useful for debugging purposes
var Min = t.refinement(t.String, function (s) { return s.length > 2; }, 'Min');
var Max = t.refinement(t.String, function (s) { return s.length < 5; }, 'Max');
var MinMax = t.intersection([Min, Max], 'MinMax');
MinMax.is('abc'); // => true
MinMax.is('a'); // => false
MinMax.is('abcde'); // => false
Intersections have the following meta
object:
{
kind: 'intersection',
types: types
}
In tcomb optional values of type T
can be represented by union([Nil, T])
. Since it's very common to handle optional values, tcomb provide an ad-hoc combinator.
You can define a maybe type using the maybe(type, name)
combinator where:
type
is the wrapped typename
: is an optional string useful for debugging purposes
// the value of a radio input where null = no selection
var Radio = t.maybe(t.String);
Radio.is('a'); // => true
Radio.is(null); // => true
Radio.is(1); // => false
Maybes have the following meta
object:
{
kind: 'maybe',
type: type
}
Typed functions may be defined like this:
// add takes two `t.Number`s and returns a `t.Number`
var add = t.func([t.Number, t.Number], t.Number)
.of(function (x, y) { return x + y; });
And used like this:
add("Hello", 2); // Raises error: Invalid `Hello` supplied to `t.Number`
add("Hello"); // Raises error: Invalid `Hello` supplied to `t.Number`
add(1, 2); // Returns: 3
add(1)(2); // Returns: 3
You can define a typed function using the func(domain, codomain, name?)
combinator where:
domain
is the type of the function's argument (or list of types of the function's arguments)codomain
is the type of the function's return valuename
: is an optional string useful for debugging purposes
Returns a function type whose functions have their domain and codomain specified and constrained.
func
can be used to define function types using native types:
// An `A` takes a `t.String` and returns an `t.Number`
var A = t.func(t.String, t.Number);
The domain and codomain can also be specified using types from any combinator including func
:
// A `B` takes a `Func` (which takes a `t.String` and returns a `t.Number`) and returns a `t.String`.
var B = t.func(t.func(t.String, t.Number), t.String);
// An `ExcitedString` is a `t.String` containing an exclamation mark
var ExcitedString = t.refinement(t.String, function (s) { return s.indexOf('!') !== -1; }, 'ExcitedString');
// An `Exciter` takes a `t.String` and returns an `ExcitedString`
var Exciter = t.func(t.String, ExcitedString);
Additionally the domain can be expressed as a list of types:
// A `C` takes an `A`, a `B` and a `t.String` and returns a `t.Number`
var C = t.func([A, B, t.String], t.Number);
Functions have the following meta
object:
{
kind: 'func',
domain: domain,
codomain: codomain
}
func(A, B).of(f);
Returns a function where the domain and codomain are typechecked against the function type.
If the function is passed values which are outside of the domain or returns values which are outside of the codomain it will raise an error:
var simpleQuestionator = Exciter.of(function (s) { return s + '?'; });
var simpleExciter = Exciter.of(function (s) { return s + '!'; });
// Raises error:
// Invalid `Hello?` supplied to `ExcitedString`, insert a valid value for the refinement
simpleQuestionator('Hello');
// Raises error: Invalid `1` supplied to `String`
simpleExciter(1);
// Returns: 'Hello!'
simpleExciter('Hello');
The returned function may also be partially applied passing a curried
additional param:
// We can reasonably suggest that add has the following type signature
// add : t.Number -> t.Number -> t.Number
var add = t.func([t.Number, t.Number], t.Number)
.of(function (x, y) { return x + y }, true);
var addHello = add("Hello"); // As this raises: "Error: Invalid `Hello` supplied to `t.Number`"
var add2 = add(2);
add2(1); // And this returns: 3
func(A, B).is(x);
Returns true
if x belongs to the type.
Exciter.is(simpleExciter); // Returns: true
Exciter.is(simpleQuestionator); // Returns: true
var id = function (x) { return x; };
t.func([t.Number, t.Number], t.Number).is(func([t.Number, t.Number], t.Number).of(id)); // Returns: true
t.func([t.Number, t.Number], t.Number).is(func(t.Number, t.Number).of(id)); // Returns: false
- Typed functions' domains are checked when they are called
- Typed functions' codomains are checked when they return
- The domain and codomain of a typed function's type is checked when the typed function is passed to a function type (such as when used as an argument in another typed function)
t.declare([name])
declares a type name to be used in other combinators without requiring a definition right away. This enables the construction of recursive or mutually recursive types.
var Tree = t.declare('Tree');
Tree.define(t.struct({
value: t.Number,
left: t.maybe(Tree),
right: t.maybe(Tree)
}));
var bst = Tree({
value: 5,
left: Tree({
value: 2
}),
right: Tree({
left: Tree({
value: 6
}),
value: 7
})
});
var A = t.declare('A');
var B = t.struct({
a: t.maybe(A)
});
A.define(t.struct({
b: t.maybe(B)
}));
You can update an immutable instance with the provided update
function:
MyType.update(instance, spec)
The following commands are compatible with the Facebook Immutability Helpers:
$push
$unshift
$splice
$set
$apply
$merge
Example:
var p = new Point({x: 1, y: 2});
p = Point.update(p, {x: {'$set': 3}}); // => {x: 3, y: 2}
var MyType = dict(t.String, t.Number);
var instance = MyType({a: 1, b: 2});
var updated = MyType.update(instance, {$remove: ['a']}); // => {b: 2}
var MyType = list(t.Number);
var instance = MyType([1, 2, 3, 4]);
var updated = MyType.update(instance, {'$swap': {from: 1, to: 2}}); // => [1, 3, 2, 4]
You can add your custom commands updating the t.update.commands
hash.
match(x: t.Any, cases...)
where each case has the following structure
type, [guard], block
type
a tcomb typeguard
an optional predicate(x) => t.Any
block
a function(x) => t.Any
called when the match succeded
Example:
// this example uses ES6 syntax
const A = t.struct({...});
const result = t.match(1,
t.String, (s) => 'a string',
t.Number, (n) => n > 2, (n) => 'a number gt 2', // case with a guard (optional)
t.Number, (n) => 'a number lte 2',
A, (a) => 'an instance of A',
t.Any, (x) => 'other...' // catch all
);
console.log(result); // => 'a number lte 2'
Note. If a match is not found it will fail with a Match error
.
There is a bunch of functions used internally by tcomb which are exported for convenience:
Called when an assert fails. The default behaviour when failures occur is to throw a TypeError:
function fail(message) {
throw new TypeError(message);
}
You can override the default behaviour re-defining the t.fail
function:
t.fail = function (message) {
// your code here
};
Immutability helper.
You can override the default behaviour re-defining the t.update
function:
t.update = function (instance, spec) {
// your code here
};
Returns the name of a tcomb type:
t.getTypeName(String); // => 'String'
If a name is not specified when defining the type, a default name will be provided according to http://flowtype.org.
Safe version of mixin, properties cannot be overwritten...
t.mixin({a: 1}, {b: 2}); // => {a: 1, b: 2}
t.mixin({a: 1}, {a: 2}); // => throws
...unless override = true
Used internally to format the error messages. Since it uses by default JSON.stringify
and can be slow in a performance intensive application so you may want to override it:
// override with a less verbose but much faster function
t.stringify = String;