From 9b84eb863f40f261e939334a3f9255747ab76c05 Mon Sep 17 00:00:00 2001 From: impinball Date: Sun, 11 Sep 2016 03:44:58 -0400 Subject: [PATCH 1/2] Unbreak things --- id.js | 2 +- id_test.js | 9 ++++++--- laws/chain.js | 4 ++-- laws/semigroup.js | 2 +- laws/traversable.js | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/id.js b/id.js index e0e5cd4..a9c7e69 100644 --- a/id.js +++ b/id.js @@ -42,7 +42,7 @@ Id.prototype[fl.ap] = function(b) { // Traversable Id.prototype[fl.sequence] = function(of) { // the of argument is only provided for types where map might fail. - return this.value.map(Id[fl.of]); + return this.value[fl.map](Id[fl.of]); }; // Chain diff --git a/id_test.js b/id_test.js index 75f2eb4..c683584 100644 --- a/id_test.js +++ b/id_test.js @@ -32,10 +32,10 @@ Sum.prototype[concat] = function(x) { return Sum(this.v + x.v); }; Sum.prototype[equals] = function(x) { - return this.v.equals ? this.v.equals(x.v) : this.v === x.v; + return this.v[equals] ? this.v[equals](x.v) : this.v === x.v; }; -const equality = (x, y) => x.equals ? x.equals(y) : x === y; +const equality = (x, y) => x[equals] ? x[equals](y) : x === y; const test = f => t => { t.ok(f("x")); t.done(); @@ -59,7 +59,7 @@ exports.chainRec = { equivalence: test((x) => { var predicate = a => a.length > 5 var done = Id[of] - var next = a => Id[of](a.concat([x])) + var next = a => Id[of](a[concat]([x])) var initial = [x] return chainRec.equivalence(Id)(equality)(predicate)(done)(next)(initial) }) @@ -94,6 +94,9 @@ exports.monoid = { rightIdentity: test((x) => monoid.rightIdentity(Id[of](Sum[empty]()))(equality)(Sum[of](x))) }; +// Semigroup tests are broken otherwise for this. +String.prototype[concat] = String.prototype.concat + exports.semigroup = { associativity: test((x) => semigroup.associativity(Id[of])(equality)(x)) }; diff --git a/laws/chain.js b/laws/chain.js index 6ac8515..8044fd5 100644 --- a/laws/chain.js +++ b/laws/chain.js @@ -12,8 +12,8 @@ const {of, chain} = require('..'); const associativity = t => eq => x => { const a = t[of](x)[chain](t[of])[chain](t[of]); - const b = t[of](x)[chain]((x) => t[of](x).chain(t[of])); + const b = t[of](x)[chain]((x) => t[of](x)[chain](t[of])); return eq(a, b); }; -module.exports = { associativity }; \ No newline at end of file +module.exports = { associativity }; diff --git a/laws/semigroup.js b/laws/semigroup.js index 7f2da65..1b5f3bf 100644 --- a/laws/semigroup.js +++ b/laws/semigroup.js @@ -21,4 +21,4 @@ const associativity = t => eq => x => { return eq(a, b); }; -module.exports = { associativity }; \ No newline at end of file +module.exports = { associativity }; diff --git a/laws/traversable.js b/laws/traversable.js index 409d48d..53177e4 100644 --- a/laws/traversable.js +++ b/laws/traversable.js @@ -2,7 +2,7 @@ const Id = require('../id'); const {identity} = require('fantasy-combinators'); -const {of, ap, sequence, map, equals, empty, concat} = require('..'); +const {of, ap, reduce, sequence, map, equals, empty, concat} = require('..'); const {tagged} = require('daggy'); const Compose = tagged('c'); From 181555e91b11acb2c25e48160693328dbb09bdaf Mon Sep 17 00:00:00 2001 From: impinball Date: Sun, 11 Sep 2016 04:46:47 -0400 Subject: [PATCH 2/2] De-`constructor`ify this and refactor tests accordingly. Basically, there is now one single source of truth, and `constructor` is not it. --- README.md | 75 ++++++++++++++++++++++---------------------- id.js | 52 +++++++++++++++---------------- id_test.js | 76 ++++++++++++++++++++++----------------------- laws/traversable.js | 39 +++++++++++------------ 4 files changed, 120 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 3b75e54..df2b129 100644 --- a/README.md +++ b/README.md @@ -138,11 +138,10 @@ the Semigroup specification. empty :: Monoid m => () -> m ``` -A value which has a Monoid must provide an `empty` method on itself or -its `constructor` object. The `empty` method takes no arguments: +A value which has a Monoid must provide an `empty` method. The `empty` +method takes no arguments: m.empty() - m.constructor.empty() 1. `empty` must return a value of the same Monoid @@ -213,11 +212,10 @@ implement the Apply specification. of :: Applicative f => a -> f a ``` -A value which has an Applicative must provide an `of` method on itself -or its `constructor` object. The `of` method takes one argument: +A value which has an Applicative must provide an `of` method. The `of` +method takes one argument: a.of(b) - a.constructor.of(b) 1. `of` must provide a value of the same Applicative @@ -254,27 +252,25 @@ implement the Functor and Foldable specifications. 1. `t(u.sequence(f.of))` is equivalent to `u.map(t).sequence(g.of)` for any `t` such that `t(a).map(f)` is equivalent to `t(a.map(f))` (naturality) -2. `u.map(F.of).sequence(F.of)` is equivalent to `F.of(u)` for any Applicative `F` (identity) +2. `u.map(f.of).sequence(f.of)` is equivalent to `f.of(u)` for any Applicative `f` (identity) -3. `u.map(x => new Compose(x)).sequence(Compose.of)` is equivalent to - `new Compose(u.sequence(F.of).map(v => v.sequence(G.of)))` for `Compose` defined below and any Applicatives `F` and `G` (composition) +3. `u.map(compose.of).sequence(compose.of)` is equivalent to + `compose.create(u.sequence(f.of).map(v => v.sequence(g.of)))` for `Compose` defined below and any Applicatives `f` and `g` (composition) ```js -var Compose = function(c) { - this.c = c; -}; - -Compose.of = function(x) { - return new Compose(F.of(G.of(x))); -}; - -Compose.prototype.ap = function(x) { - return new Compose(this.c.map(u => y => u.ap(y)).ap(x.c)); -}; - -Compose.prototype.map = function(f) { - return new Compose(this.c.map(y => y.map(f))); -}; +var compose = { + of: function(x) { + return {__proto__: compose, c}; + }, + + ap: function(x) { + return compose.create(this.c.map(u => y => u.ap(y)).ap(x.c)); + }, + + map: function(f) { + return compose.create(this.c.map(y => y.map(f))); + }, +} ``` #### `sequence` method @@ -331,11 +327,10 @@ A value that implements the ChainRec specification must also implement the Chain chainRec :: ChainRec m => ((a -> c) -> (b -> c) -> a -> m c) -> a -> m b ``` -A Type which has a ChainRec must provide an `chainRec` method on itself -or its `constructor` object. The `chainRec` method takes two arguments: +A Type which has a ChainRec must provide an `chainRec` method. The +`chainRec` method takes two arguments: a.chainRec(f, i) - a.constructor.chainRec(f, i) 1. `f` must be a function which returns a value 1. If `f` is not a function, the behaviour of `chainRec` is unspecified. @@ -501,19 +496,23 @@ to implement certain methods then derive the remaining methods. Derivations: ```js function(f, acc) { - function Const(value) { - this.value = value; + function makeConst(value) { + return {__proto__: constant, value}; } - Const.of = function(_) { - return new Const(acc); - }; - Const.prototype.map = function(_) { - return this; - }; - Const.prototype.ap = function(b) { - return new Const(f(this.value, b.value)); + + var constant = { + of: function(_) { + return makeConst(acc); + }, + map: function(_) { + return this; + }, + ap: function(b) { + return makeConst(f(this.value, b.value)); + }, }; - return this.map(x => new Const(x)).sequence(Const.of).value; + + return this.map(makeConst).sequence(constant.of).value; } ``` diff --git a/id.js b/id.js index a9c7e69..aeacbe7 100644 --- a/id.js +++ b/id.js @@ -1,27 +1,30 @@ -var fl = require('./') +var fl = require('./'); function Id(a) { - this.value = a; + const inst = Object.create(id); + inst.value = a; + return inst; } +const id = Id.prototype; + // Setoid -Id.prototype[fl.equals] = function(b) { +id[fl.equals] = function(b) { return typeof this.value[fl.equals] === "function" ? this.value[fl.equals](b.value) : this.value === b.value; }; // Semigroup (value must also be a Semigroup) -Id.prototype[fl.concat] = function(b) { - return new Id(this.value[fl.concat](b.value)); +id[fl.concat] = function(b) { + return id[fl.of](this.value[fl.concat](b.value)); }; // Monoid (value must also be a Monoid) -Id[fl.empty] = function() { - return new Id(this.value[fl.empty] ? this.value[fl.empty]() : this.value.constructor[fl.empty]()); +id[fl.empty] = function() { + return id[fl.of](this.value[fl.empty]()); }; -Id.prototype[fl.empty] = Id[fl.empty]; // Foldable -Id.prototype[fl.reduce] = function(f, acc) { +id[fl.reduce] = function(f, acc) { return f(acc, this.value); }; @@ -30,52 +33,49 @@ Id.prototype.toArray = function() { }; // Functor -Id.prototype[fl.map] = function(f) { - return new Id(f(this.value)); +id[fl.map] = function(f) { + return id[fl.of](f(this.value)); }; // Apply -Id.prototype[fl.ap] = function(b) { - return new Id(this.value(b.value)); +id[fl.ap] = function(b) { + return id[fl.of](this.value(b.value)); }; // Traversable -Id.prototype[fl.sequence] = function(of) { +id[fl.sequence] = function(of) { // the of argument is only provided for types where map might fail. - return this.value[fl.map](Id[fl.of]); + return this.value[fl.map](this[fl.of]); }; // Chain -Id.prototype[fl.chain] = function(f) { +id[fl.chain] = function(f) { return f(this.value); }; // ChainRec -Id[fl.chainRec] = function(f, i) { +id[fl.chainRec] = function(f, i) { var state = { done: false, value: i}; var next = (v) => ({ done: false, value: v }); var done = (v) => ({ done: true, value: v }); while (state.done === false) { state = f(next, done, state.value).value; } - return new Id(state.value); + return id[fl.of](state.value); }; -Id.prototype[fl.chainRec] = Id[fl.chainRec]; // Extend -Id.prototype[fl.extend] = function(f) { - return new Id(f(this)); +id[fl.extend] = function(f) { + return id[fl.of](f(this)); }; // Applicative -Id[fl.of] = function(a) { - return new Id(a); -}; -Id.prototype[fl.of] = Id[fl.of]; +id[fl.of] = Id; // Comonad -Id.prototype[fl.extract] = function() { +id[fl.extract] = function() { return this.value; }; module.exports = Id; +module.exports.id = id; diff --git a/id_test.js b/id_test.js index c683584..256f7d4 100644 --- a/id_test.js +++ b/id_test.js @@ -17,21 +17,19 @@ const traversable = require('./laws/traversable'); const {tagged} = require('daggy'); const {of, empty, concat, equals, map} = require('.'); -const Id = require('./id'); +const {id} = require('./id'); // Special type of sum for the type of string. -const Sum = tagged('v'); -Sum[of] = (x) => Sum(x); -Sum[empty] = () => Sum(''); -Sum.prototype[of] = Sum[of]; -Sum.prototype[empty] = Sum[empty]; -Sum.prototype[map] = function(f) { - return Sum(f(this.v)); -}; -Sum.prototype[concat] = function(x) { - return Sum(this.v + x.v); -}; -Sum.prototype[equals] = function(x) { +const sum = {}; +sum[of] = (v) => ({__proto__: sum, v}); +sum[empty] = () => sum[of](''); +sum[map] = function(f) { + return sum[of](f(this.v)); +}; +sum[concat] = function(x) { + return sum[of](this.v + x.v); +}; +sum[equals] = function(x) { return this.v[equals] ? this.v[equals](x.v) : this.v === x.v; }; @@ -42,73 +40,73 @@ const test = f => t => { }; exports.applicative = { - identity: test((x) => applicative.identity(Id)(equality)(x)), - homomorphism: test((x) => applicative.homomorphism(Id)(equality)(x)), - interchange: test((x) => applicative.interchange(Id)(equality)(x)) + identity: test((x) => applicative.identity(id)(equality)(x)), + homomorphism: test((x) => applicative.homomorphism(id)(equality)(x)), + interchange: test((x) => applicative.interchange(id)(equality)(x)) }; exports.apply = { - composition: test((x) => apply.composition(Id)(equality)(x)) + composition: test((x) => apply.composition(id)(equality)(x)) }; exports.chain = { - associativity: test((x) => chain.associativity(Id)(equality)(x)) + associativity: test((x) => chain.associativity(id)(equality)(x)) }; exports.chainRec = { equivalence: test((x) => { var predicate = a => a.length > 5 - var done = Id[of] - var next = a => Id[of](a[concat]([x])) + var done = id[of] + var next = a => id[of](a[concat]([x])) var initial = [x] - return chainRec.equivalence(Id)(equality)(predicate)(done)(next)(initial) + return chainRec.equivalence(id)(equality)(predicate)(done)(next)(initial) }) }; exports.comonad = { - leftIdentity: test((x) => comonad.leftIdentity(Id[of])(equality)(x)), - rightIdentity: test((x) => comonad.rightIdentity(Id[of])(equality)(x)), - associativity: test((x) => comonad.associativity(Id[of])(equality)(x)) + leftIdentity: test((x) => comonad.leftIdentity(id[of])(equality)(x)), + rightIdentity: test((x) => comonad.rightIdentity(id[of])(equality)(x)), + associativity: test((x) => comonad.associativity(id[of])(equality)(x)) }; exports.extend = { - associativity: test((x) => extend.associativity(Id[of])(equality)(x)) + associativity: test((x) => extend.associativity(id[of])(equality)(x)) }; exports.foldable = { - associativity: test((x) => foldable.associativity(Id[of])(equality)(x)) + associativity: test((x) => foldable.associativity(id[of])(equality)(x)) }; exports.functor = { - identity: test((x) => functor.identity(Id[of])(equality)(x)), - composition: test((x) => functor.composition(Id[of])(equality)(x)) + identity: test((x) => functor.identity(id[of])(equality)(x)), + composition: test((x) => functor.composition(id[of])(equality)(x)) }; exports.monad = { - leftIdentity: test((x) => monad.leftIdentity(Id)(equality)(x)), - rightIdentity: test((x) => monad.rightIdentity(Id)(equality)(x)) + leftIdentity: test((x) => monad.leftIdentity(id)(equality)(x)), + rightIdentity: test((x) => monad.rightIdentity(id)(equality)(x)) }; exports.monoid = { - leftIdentity: test((x) => monoid.leftIdentity(Id[of](Sum[empty]()))(equality)(Sum[of](x))), - rightIdentity: test((x) => monoid.rightIdentity(Id[of](Sum[empty]()))(equality)(Sum[of](x))) + leftIdentity: test((x) => monoid.leftIdentity(id[of](sum[empty]()))(equality)(sum[of](x))), + rightIdentity: test((x) => monoid.rightIdentity(id[of](sum[empty]()))(equality)(sum[of](x))) }; // Semigroup tests are broken otherwise for this. String.prototype[concat] = String.prototype.concat exports.semigroup = { - associativity: test((x) => semigroup.associativity(Id[of])(equality)(x)) + associativity: test((x) => semigroup.associativity(id[of])(equality)(x)) }; exports.setoid = { - reflexivity: test((x) => setoid.reflexivity(Id[of])(equality)(x)), - symmetry: test((x) => setoid.symmetry(Id[of])(equality)(x)), - transitivity: test((x) => setoid.transitivity(Id[of])(equality)(x)) + reflexivity: test((x) => setoid.reflexivity(id[of])(equality)(x)), + symmetry: test((x) => setoid.symmetry(id[of])(equality)(x)), + transitivity: test((x) => setoid.transitivity(id[of])(equality)(x)) }; exports.traversable = { - naturality: test((x) => traversable.naturality(Id[of])(equality)(Id[of](x))), - identity: test((x) => traversable.identity(Id[of])(equality)(x)), - composition: test((x) => traversable.composition(Id[of])(equality)(Id[of](Sum[of](x)))) + naturality: test((x) => traversable.naturality(id[of])(equality)(id[of](x))), + identity: test((x) => traversable.identity(id[of])(equality)(x)), + composition: test((x) => traversable.composition(id[of])(equality)(id[of](sum[of](x)))) }; diff --git a/laws/traversable.js b/laws/traversable.js index 53177e4..7905933 100644 --- a/laws/traversable.js +++ b/laws/traversable.js @@ -1,21 +1,22 @@ 'use strict'; -const Id = require('../id'); +const {id} = require('../id'); const {identity} = require('fantasy-combinators'); const {of, ap, reduce, sequence, map, equals, empty, concat} = require('..'); -const {tagged} = require('daggy'); -const Compose = tagged('c'); -Compose[of] = Compose; -Compose.prototype[ap] = function(x) { - return Compose(this.c[map](u => y => u[ap](y))[ap](x.c)); -}; -Compose.prototype[map] = function(f) { - return Compose(this.c[map](y => y[map](f))); -}; -Compose.prototype[equals] = function(x) { +const compose = {} +compose[of] = function(c) { + return {__proto__: compose, c}; +} +compose[ap] = function(x) { + return compose[of](this.c[map](u => y => u[ap](y))[ap](x.c)); +} +compose[map] = function(f) { + return compose[of](this.c[map](y => y[map](f))); +} +compose[equals] = function(x) { return this.c[equals] ? this.c[equals](x.c) : this.c === x.c; -}; +} Array.prototype[equals] = function(y) { return this.length === y.length && this.join('') === y.join(''); @@ -38,10 +39,10 @@ Array.prototype[sequence] = function(p) { 1. `t(u.sequence(f.of))` is equivalent to `u.map(t).sequence(g.of)` where `t` is a natural transformation from `f` to `g` (naturality) -2. `u.map(x => Id(x)).sequence(Id.of)` is equivalent to `Id.of` (identity) +2. `u.map(id.of).sequence(id.of)` is equivalent to `id.of` (identity) -3. `u.map(Compose).sequence(Compose.of)` is equivalent to - `Compose(u.sequence(f.of).map(x => x.sequence(g.of)))` (composition) +3. `u.map(compose.of).sequence(compose.of)` is equivalent to + `compose.of(u.sequence(f.of).map(x => x.sequence(g.of)))` (composition) */ @@ -54,14 +55,14 @@ const naturality = t => eq => x => { const identityʹ = t => eq => x => { const u = [x]; - const a = u[map](Id[of])[sequence](Id[of]); - const b = Id[of](u); + const a = u[map](id[of])[sequence](id[of]); + const b = id[of](u); return eq(a, b); }; const composition = t => eq => x => { - const a = t(x)[map](Compose)[sequence](Compose[of]); - const b = Compose(t(x)[sequence](Id[of])[map](x => x[sequence](Id[of]))); + const a = t(x)[map](compose[of])[sequence](compose[of]); + const b = compose[of](t(x)[sequence](id[of])[map](x => x[sequence](id[of]))); return eq(a, b); };