UNPKG

adt-simple

Version:

Algebraic data types for JavaScript using Sweet.js macros

324 lines (299 loc) 8.24 kB
/*! * adt-simple * ---------- * author: Nathan Faubion <nathan@n-son.com> * version: 0.1.3 * license: MIT */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('adt-simple', factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.adt = exports; } })(this, function () { var Eq = { nativeEquals: function(a, b) { return a === b; }, derive: eachVariant(function(v) { if (v.fields) { v.prototype.equals = function(that) { if (this === that) return true; if (!(that instanceof v.constructor)) return false; for (var i = 0, len = v.fields.length; i < len; i++) { var f = v.fields[i]; var vala = this[f]; var valb = that[f]; if (vala && vala.equals) { if (!vala.equals(valb)) return false; } else if (!Eq.nativeEquals(vala, valb)) { return false; } } return true; }; } else { v.prototype.equals = function(that) { return this === that; } } }) }; var Clone = { nativeClone: function(a) { return a; }, derive: eachVariant(function(v) { if (v.fields) { v.prototype.clone = function() { var self = this; var args = map(v.fields, function(f) { var val = self[f]; return val && val.clone ? val.clone() : Clone.nativeClone(val); }); return unrollApply(v.constructor, args); }; } else { v.prototype.clone = function() { return this; }; } }) }; var Setter = { derive: eachVariant(function(v, adt) { if (v.fields) { v.constructor.create = function(obj) { var args = map(v.fields, function(f) { if (!obj.hasOwnProperty(f)) { throw new TypeError('Missing field: ' + [adt.name, v.name, f].join('.')); } return obj[f]; }); return unrollApply(v.constructor, args); }; v.prototype.set = function(obj) { var self = this; var args = map(v.fields, function(f) { return obj.hasOwnProperty(f) ? obj[f] : self[f]; }); return unrollApply(v.constructor, args); }; } }) }; var ToString = { toString: function(x) { if (x === null) return 'null'; if (x === void 0) return 'undefined'; if (Object.prototype.toString.call(x) === '[object Array]') { return '[' + map(x, function(v) { return ToString.toString(v); }).join(', ') + ']'; } return x.toString(); }, derive: eachVariant(function(v) { if (v.fields) { v.prototype.toString = function() { var self = this; return v.name + '(' + map(v.fields, function(f) { return ToString.toString(self[f]); }).join(', ') + ')'; }; } else { v.prototype.toString = function() { return v.name; }; } }) }; var ToJSON = { toJSONValue: function(x) { return x && typeof x === 'object' && x.toJSON ? x.toJSON() : x; }, derive: eachVariant(function(v) { if (v.fields) { v.prototype.toJSON = function() { var res = {}; var self = this; each(v.fields, function(f) { res[f] = ToJSON.toJSONValue(self[f]); }); return res; } } else { v.prototype.toJSON = function() { return this.hasOwnProperty('value') ? this.value : v.name }; } }) }; var Curry = { derive: eachVariant(function(v, adt) { if (v.fields && v.fields.length) { var ctr = v.constructor; function curried() { var args = arguments; if (args.length < v.fields.length) { return function() { return unrollApply(curried, concat(args, arguments)); }; } var res = unrollApply(ctr, args); return res; }; v.constructor = curried; v.constructor.prototype = ctr.prototype; v.prototype.constructor = curried; if (adt.constructor === ctr) { adt.constructor = v.constructor; for (var k in ctr) { if (ctr.hasOwnProperty(k)) { adt.constructor[k] = ctr[k]; } } } } }) }; var Extractor = { derive: eachVariant(function(v) { if (v.fields) { v.constructor.hasInstance = function(x) { return x && x.constructor === v.constructor; }; v.constructor.unapply = function(x) { if (v.constructor.hasInstance(x)) { return map(v.fields, function(f) { return x[f]; }); } }; v.constructor.unapplyObject = function(x) { if (v.constructor.hasInstance(x)) { var res = {}; each(v.fields, function(f) { res[f] = x[f] }); return res; } }; } else { v.prototype.hasInstance = function(x) { return x === this; }; } }) }; var Reflect = { derive: function(adt) { adt.constructor.__names__ = map(adt.variants, function(v) { v.prototype['is' + v.name] = true; v.constructor.__fields__ = v.fields ? v.fields.slice() : null; return v.name; }); return adt; } }; var Cata = { derive: eachVariant(function(v, adt) { v.prototype.cata = function(dispatch) { if (!dispatch.hasOwnProperty(v.name)) { throw new TypeError('No branch for: ' + [adt.name, v.name].join('.')); } var self = this; var args = v.fields ? map(v.fields, function(f) { return self[f] }) : []; return dispatch[v.name].apply(this, args); }; }) }; var LateDeriving = { derive: function(adt) { // Singleton data constructors need it on the prototype var ctr = adt.variants && adt.variants[0] && adt.variants[0].constructor === adt.constructor && !adt.variants[0].fields ? adt.prototype : adt.constructor ctr.deriving = function() { var res = adt; for (var i = 0, c; c = arguments[i]; i++) { res = c.derive(res); } } return adt; } }; var Base = composeDeriving(Eq, Clone, Setter, ToString, Reflect, Extractor); // Export // ------ return { eachVariant: eachVariant, composeDeriving: composeDeriving, Eq: Eq, Clone: Clone, Setter: Setter, ToString: ToString, ToJSON: ToJSON, Curry: Curry, Extractor: Extractor, Reflect: Reflect, Cata: Cata, LateDeriving: LateDeriving, Base: Base }; // Utilities // --------- function each(arr, fn) { for (var i = 0, len = arr.length; i < len; i++) { fn(arr[i], i, arr); } } function map(arr, fn) { var res = []; for (var i = 0, len = arr.length; i < len; i++) { res[res.length] = fn(arr[i], i, arr); } return res; } function eachVariant(fn) { return function(adt) { each(adt.variants, function(v) { fn(v, adt); }); return adt; } } function composeDeriving() { var classes = arguments; return { derive: function(adt) { var res = adt; for (var i = 0, len = classes.length; i < len; i++) { res = classes[i].derive(res); } return res; } }; } function unrollApply(fn, a) { switch (a.length) { case 0: return fn(); case 1: return fn(a[0]); case 2: return fn(a[0], a[1]); case 3: return fn(a[0], a[1], a[2]); case 4: return fn(a[0], a[1], a[2], a[3]); default: return fn.apply(null, a); } } function concat(a, b) { var res = [], i, len; for (i = 0, len = a.length; i < len; i++) res[res.length] = a[i]; for (i = 0, len = b.length; i < len; i++) res[res.length] = b[i]; return res; } });