Skip to content

Commit

Permalink
De-constructorify this and refactor tests accordingly.
Browse files Browse the repository at this point in the history
Basically, there is now one single source of truth, and `constructor` is not
it.
  • Loading branch information
impinball committed Sep 11, 2016
1 parent 9b84eb8 commit 181555e
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 122 deletions.
75 changes: 37 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
```

Expand Down
52 changes: 26 additions & 26 deletions id.js
Original file line number Diff line number Diff line change
@@ -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);
};

Expand All @@ -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;
76 changes: 37 additions & 39 deletions id_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand All @@ -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))))
};
Loading

0 comments on commit 181555e

Please sign in to comment.