Skip to content

Commit

Permalink
Merge pull request #123 from fantasyland/laws
Browse files Browse the repository at this point in the history
Add the laws implementations
  • Loading branch information
SimonRichardson committed Jan 29, 2016
2 parents a25c3da + 433281d commit 388a038
Show file tree
Hide file tree
Showing 17 changed files with 504 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ implement the Functor specification.
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(x => Id(x)).sequence(Id.of)` is equivalent to `Id.of(u)` (identity)

3. `u.map(Compose).sequence(Compose.of)` is equivalent to
`Compose(u.sequence(f.of).map(x => x.sequence(g.of)))` (composition)
Expand Down
12 changes: 9 additions & 3 deletions id.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ Id.prototype[fl.concat] = function(b) {
};

// Monoid (value must also be a Monoid)
Id.prototype[fl.empty] = function() {
Id[fl.empty] = function() {
return new Id(this.value[fl.empty] ? this.value[fl.empty]() : this.value.constructor[fl.empty]());
};
Id.prototype[fl.empty] = Id[fl.empty];

// Foldable
Id.prototype[fl.reduce] = function(f, acc) {
return f(acc, this.value);
};

Id.prototype.toArray = function() {
return [this.value];
};

// Functor
Id.prototype[fl.map] = function(f) {
return new Id(f(this.value));
Expand All @@ -37,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.of);
return this.value.map(Id[fl.of]);
};

// Chain
Expand All @@ -54,10 +59,11 @@ Id.prototype[fl.extend] = function(f) {
Id[fl.of] = function(a) {
return new Id(a);
};
Id.prototype[fl.of] = Id[fl.of];

// Comonad
Id.prototype[fl.extract] = function() {
return this.value;
};

if (typeof module == 'object') module.exports = Id;
module.exports = Id;
100 changes: 100 additions & 0 deletions id_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use strict';

const applicative = require('./laws/applicative');
const apply = require('./laws/apply');
const chain = require('./laws/chain');
const comonad = require('./laws/comonad');
const extend = require('./laws/extend');
const foldable = require('./laws/foldable');
const functor = require('./laws/functor');
const monad = require('./laws/monad');
const monoid = require('./laws/monoid');
const semigroup = require('./laws/semigroup');
const setoid = require('./laws/setoid');
const traversable = require('./laws/traversable');

const {tagged} = require('daggy');
const {of, empty, concat, equals, map} = require('.');

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) {
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 test = f => t => {
t.ok(f("x"));
t.done();
};

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))
};

exports.apply = {
composition: test((x) => apply.composition(Id)(equality)(x))
};

exports.chain = {
associativity: test((x) => chain.associativity(Id)(equality)(x))
};

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))
};

exports.extend = {
associativity: test((x) => extend.associativity(Id.of)(equality)(x))
};

exports.foldable = {
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))
};

exports.monad = {
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)))
};

exports.semigroup = {
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))
};

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))))
};
39 changes: 39 additions & 0 deletions laws/applicative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

const {identity, thrush} = require('fantasy-combinators');
const {of, ap} = require('..');

/**
### Applicative
1. `a.of(x => x).ap(v)` is equivalent to `v` (identity)
2. `a.of(f).ap(a.of(x))` is equivalent to `a.of(f(x))` (homomorphism)
3. `u.ap(a.of(y))` is equivalent to `a.of(f => f(y)).ap(u)` (interchange)
**/

const identityʹ = t => eq => x => {
const a = t[of](identity).ap(t[of](x));
const b = t[of](x);
return eq(a, b);
};

const homomorphism = t => eq => x => {
const a = t[of](identity).ap(t[of](x));
const b = t[of](identity(x));
return eq(a, b);
};

const interchange = t => eq => x => {
const u = t[of](identity);

const a = u.ap(t[of](x));
const b = t[of](thrush(x)).ap(u);
return eq(a, b);
};

module.exports = { identity: identityʹ
, homomorphism
, interchange
};
22 changes: 22 additions & 0 deletions laws/apply.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const {identity, compose} = require('fantasy-combinators');
const {of, map, ap} = require('..');

/**
### Apply
1. `a.map(f => g => x => f(g(x))).ap(u).ap(v)` is equivalent to `a.ap(u.ap(v))` (composition)
**/

const composition = t => eq => x => {
const y = t[of](identity);

const a = y[map](compose)[ap](y)[ap](y);
const b = y[ap](y[ap](y));
return eq(a, b);
};

module.exports = { composition };
19 changes: 19 additions & 0 deletions laws/chain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

const {of, chain} = require('..');

/**
### Chain
1. `m.chain(f).chain(g)` is equivalent to `m.chain(x => f(x).chain(g))` (associativity)
**/

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]));
return eq(a, b);
};

module.exports = { associativity };
37 changes: 37 additions & 0 deletions laws/comonad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

const {identity} = require('fantasy-combinators');
const {extend} = require('..');

/**
### Comonad
1. `w.extend(_w => _w.extract())` is equivalent to `w`
2. `w.extend(f).extract()` is equivalent to `f(w)`
3. `w.extend(f)` is equivalent to `w.extend(x => x).map(f)`
**/

const leftIdentity = t => eq => x => {
const a = t(x).extend(identity).extract();
const b = t(x);
return eq(a, b);
};

const rightIdentity = t => eq => x => {
const a = t(x).extend(w => w.extract());
const b = t(x);
return eq(a, b);
};

const associativity = t => eq => x => {
const a = t(x).extend(identity);
const b = t(x).extend(identity).map(identity);
return eq(a, b);
};

module.exports = { leftIdentity
, rightIdentity
, associativity
};
20 changes: 20 additions & 0 deletions laws/extend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

const {identity} = require('fantasy-combinators');
const {extend} = require('..');

/**
### Extend
1. `w.extend(g).extend(f)` is equivalent to `w.extend(_w => f(_w.extend(g)))`
**/

const associativity = t => eq => x => {
const a = t(x).extend(identity).extend(identity);
const b = t(x).extend(w => identity(w.extend(identity)));
return eq(a, b);
};

module.exports = { associativity };
19 changes: 19 additions & 0 deletions laws/foldable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

const {identity} = require('fantasy-combinators');

/**
### Foldable
1. `u.reduce` is equivalent to `u.toArray().reduce`
**/

const associativity = t => eq => x => {
const a = t(x).reduce(identity, x);
const b = t(x).toArray().reduce(identity, x);
return eq(a, b);
};

module.exports = { associativity };
29 changes: 29 additions & 0 deletions laws/functor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const {identity, compose} = require('fantasy-combinators');
const {map} = require('..');

/*
### Functor
1. `u.map(a => a)` is equivalent to `u` (identity)
2. `u.map(x => f(g(x)))` is equivalent to `u.map(g).map(f)` (composition)
*/

const identityʹ = t => eq => x => {
const a = t(x)[map](identity);
const b = t(x);
return eq(a, b);
};

const composition = t => eq => x => {
const a = t(x)[map](compose(identity)(identity));
const b = t(x)[map](identity)[map](identity);
return eq(a, b);
};

module.exports = { identity: identityʹ
, composition
};
29 changes: 29 additions & 0 deletions laws/monad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const {identity, apply} = require('fantasy-combinators');
const {of, chain} = require('..');

/**
### Monad
1. `m.of(a).chain(f)` is equivalent to `f(a)` (left identity)
2. `m.chain(m.of)` is equivalent to `m` (right identity)
**/

const leftIdentity = t => eq => x => {
const a = t[of](x)[chain](identity);
const b = identity(x);
return eq(a, b);
};

const rightIdentity = t => eq => x => {
const a = t[of](x)[chain](t[of]);
const b = t[of](x);
return eq(a, b);
};

module.exports = { leftIdentity
, rightIdentity
};
Loading

0 comments on commit 388a038

Please sign in to comment.