Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single source of truth for everything #158

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we all agree that this change is a good idea. I've opened #162 which makes this change only.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something wrong here. new Compose is intentionally not the same as Compose.of see #141

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix that when I get home to compose.create (the intended replacement method).

`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};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like a different method of setting the prototype? (Keep in mind, I'm not actually that deeply invested in this. I'm merely encouraging exploration.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not rely on the constructor property as we currently do? We could write:

//  Identity :: a -> Identity a
var Identity = function Identity(x) {
  return {
    constructor: {'fantasy-land/of': Identity},
    'fantasy-land/chain': function(f) { return f(x); },
    'fantasy-land/map': function(f) { return Identity(f(x)); },
    // ...
    value: x
  };
};

The nice thing about this approach is it allows a prototype-based implementation:

//  Identity :: a -> Identity a
var Identity = function Identity(x) {
  if (!(this instanceof Identity)) return new Identity(x);
  this.value = x;
};

//  Identity.of :: a -> Identity a
Identity['fantasy-land/of'] = Identity;

//  Identity#chain :: Identity a ~> (a -> Identity b) -> Identity b
Identity.prototype['fantasy-land/chain'] = function(f) { return f(this.value); };

//  Identity#map :: Identity a ~> (a -> b) -> Identity b
Identity.prototype['fantasy-land/map'] = function(f) { return Identity(f(this.value)); };

// ...

I don't see a reason to use __proto__.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constructor: {'fantasy-land/of': Identity}
Identity['fantasy-land/of'] = Identity

But didn't we want to remove static method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway if we want to remove static method, I think we should simply remove static method from current implementations, without switching to simple objects or anything.

},

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.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;
79 changes: 40 additions & 39 deletions id_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,95 +17,96 @@ 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));
const sum = {};
sum[of] = (v) => ({__proto__: sum, v});
sum[empty] = () => sum[of]('');
sum[map] = function(f) {
return sum[of](f(this.v));
};
Sum.prototype[concat] = function(x) {
return Sum(this.v + x.v);
sum[concat] = function(x) {
return sum[of](this.v + x.v);
};
Sum.prototype[equals] = function(x) {
return this.v.equals ? this.v.equals(x.v) : this.v === x.v;
sum[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 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))
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))))
};
4 changes: 2 additions & 2 deletions laws/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
module.exports = { associativity };
2 changes: 1 addition & 1 deletion laws/semigroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ const associativity = t => eq => x => {
return eq(a, b);
};

module.exports = { associativity };
module.exports = { associativity };
Loading