adt-simple
Native algebraic data types for JavaScript using sweet.js macros
Features
- No required runtime dependencies
deriving
sugar for mixing in generic behavior- A catalogue of behavior mixins (
Eq
,Clone
,ToJSON
, etc)
Install
npm install -g sweet.js
npm install adt-simple
sjs -m adt-simple/macros myfile.js
Basic Usage
adt-simple exports a data
macro for creating simple data constructors.
data Singletondata SingletonVal = 42data data Employee name: String salary: Number // Singletons can have constant valuesSingletonValvalue === 42; // Positional fieldsvar tup = ;tup0 === 1;tup1 === 2; // Named fieldsvar pete = ;petename === 'Peter Gibbons';petesalary === 85000;
It also exports a union
macro for grouping your constructors.
union Maybe Nothing Just value: * // Basic inheritanceNothing instanceof Maybe; instanceof Maybe; // Constructors exported on the parentMaybeNothing === Nothing;MaybeJust === Just;
You can even build recursive unions.
union List Nil Cons head: * tail: List var list = ;listhead === 1;listtailhead === 2;listtailtailhead === 3; // TypeError('Unexpected type for field: List.Cons.tail')
adt-simple doesn't just do instance checking. You can put in your own custom constraints that validate or transform values:
{ return x;} data OnlyStrings value: toString value === '42';
It tries to do the right thing: if the identifier starts with a capital letter
(taking namespaces into consideration), it will do instance checking, otherwise
it will call the constraint as a function. The instance checking is smart about
built-in JavaScript types, so it will do proper tag checks for Boolean
,
Number
, Array
, etc beyond just instanceof
.
Deriving
adt-simple also supports a powerful sugar for deriving generic behaviour:
union Maybe Nothing Just value: *
This works for both union
and data
constructors.
Built-in Derivers
You can import built-in derivers by requiring the adt-simple
library (< 1KB).
It's available as a UMD
module, so you can use it in the browser with
require.js or with the global adt
namespace.
var Eq = Eq;
Writing Your Own Derivers
Derivers are simply objects with a derive
method. The derive
method is
called with a template of the ADT so you can traverse, inspect, extend, or
modify it. Here's what a template for a List
union would look like:
union List Nil Cons head: * tail: List name: 'List'
Here's how you might write a deriver to get positional fields on a record:
var Get = { adtvariants return adt; };
Notice how you need to return the template at the end of the function to pass on to the next deriver in the chain. You are free to mutate or tag the template as needed to communicate between derivers.
Since the above pattern is so common, you can use the eachVariant
helper to
shorten your code.
var eachVariant = eachVariant;var Get = derive: ;
You can also use composeDeriving
to compose derivers together into a single
chain.
var composeDeriving = composeDeriving;var MyDeriver = ; data deriving MyDeriver
Derivers are just expressions, so you can even parameterize them.
var { return derive: }; data deriving // logs: hello Foo(1, 2)
Compiler Pragmas
adt-simple has sensible defaults, but you can also configure the output by adding one or more pragma comments before your definition.
/* @newrequired, @scoped */union Foo Bar Baz
@newrequired
By default, constructors can be called without a new
keyword. This pragma
disables the instanceof
check that enables this behavior, leaving you with
a simpler constructor. Note: this pragma will conflict with the Curry
deriver.
@scoped
All union variants are unwrapped and put in the outer scope. This pragma disables the unwrapping and leaves them scoped to the parent.
@overrideapply
This pragma lets you define a custom apply
method on the parent constructor,
which lets you call it as a normal function.
/* @overrideapply */union List Nil Cons head: * tail: List deriving ToString List { // Turn an array into a list}; === 'Cons(1, Cons(2, Cons(3, Nil)))';
Eq
Implements an equals
method for deep equality:
data deriving Eq === true;
By default, Eq
uses reference equality for anything without an equals
method, but you can override it. For example, using lodash
:
EqnativeEquals = _isEqual; === true;
Clone
Implements a clone
method for making deep copies:
data deriving Clone var foo1 = ;var foo2 = foo1; foo1 !== foo2 && foo20 === 1;
Like with Eq
, Clone
copies by references anything without a clone
method.
You can override that behavior in a similar way. Using lodash
:
ClonenativeClone = _cloneDeep;
Setter
Extends constructors with a create
method and instances with a set
method
for setting named values. set
returns a shallow copy with the provided values
changed.
data Foo bar: * baz: * deriving Setter var foo1 = Foo;var foo2 = foo1; foo1 !== foo2;foo2bar === 43 && foo2baz === foo1baz;
ToString
Extends instances with a good toString
implementation.
union List Nil Cons head: * tail: List deriving ToString var list = ;list === 'Cons(1, Cons(2, Cons(3, Nil)))';
ToJSON
Implements a toJSON
method. You can configure how singletons are serialized
by assigning a constant value to it.
union List Nil = null Cons head: * tail: List deriving ToJSON var list = ;list head: 1 tail: head: 2 tail: head: 3 tail: null
Curry
Implements constructor currying and partial application.
data deriving Curry ;23;3;2 3;
Extractor
Implements the sparkler extractor protocol so you can pattern match on your data instances.
union List Nil Cons head: * tail: List deriving Extractor Listprototype { return match this Nil }
Reflect
Implements tag properties and field/union name reflection.
union List Nil Cons head: * tail: List deriving Reflect NilisNil === true;isCons === true; List__names__ // ['Nil', 'Cons']Cons__fields__ // ['head', 'tail']
Cata
Implements a cata
method (ala daggy)
for doing dispatching and destructuring.
union List Nil Cons head: * tail: List deriving Cata Listprototype { return this}
LateDeriving
Extends constructors with a deriving
method for deriving after-the-fact.
data Foo bar: * baz: * deriving LateDeriving Foo;
Base
A composition of Eq
, Clone
, Setter
, ToString
, Reflect
, and Extractor
.
Author
Nathan Faubion (@natefaubion)
License
MIT