Skip to content
Merged
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
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ structures:
* [Foldable](#foldable)
* [Traversable](#traversable)
* [Chain](#chain)
* [ChainRec](#chainrec)
* [Monad](#monad)
* [Extend](#extend)
* [Comonad](#comonad)
Expand Down Expand Up @@ -305,6 +306,36 @@ method takes one argument:

2. `chain` must return a value of the same Chain

### ChainRec

A value that implements the ChainRec specification must also implement the Chain specification.

1. `m.chainRec((next, done, v) => p(v) ? d(v).map(done) : n(v).map(next), i)`
is equivalent to
`(function step(v) { return p(v) ? d(v) : n(v).chain(step); }(i))` (equivalence)
2. Stack usage of `m.chainRec(f, i)` must be at most a constant multiple of the stack usage of `f` itself.

#### `chainRec` method

```hs
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.chainRec(f, i)
Copy link
Contributor

Choose a reason for hiding this comment

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

[minor] These need some triple backticks for consistency.

Copy link
Member Author

Choose a reason for hiding this comment

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

Triple brackets are used only for type signatures.

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.
2. `f` takes three arguments `next`, `done`, `value`
1. `next` is a function which takes one argument of same type as `i` and can return any value
2. `done` is a function which takes one argument and returns the same type as the return value of `next`
3. `value` is some value of the same type as `i`
3. `f` must return a value of the same ChainRec which contains a value returned from either `done` or `next`
2. `chainRec` must return a value of the same ChainRec which contains a value of same type as argument of `done`

### Monad

A value that implements the Monad specification must also implement
Expand Down Expand Up @@ -399,7 +430,7 @@ The `profunctor` method takes two arguments:
2. `f` can return any value.

2. `g` must be a function which returns a value

1. If `g` is not a function, the behaviour of `promap` is unspecified.
2. `g` can return any value.

Expand All @@ -423,7 +454,7 @@ to implement certain methods then derive the remaining methods. Derivations:
```

- [`map`][] may be derived from [`bimap`]:

```js
function(f) { return this.bimap(a => a, f); }
```
Expand Down
14 changes: 13 additions & 1 deletion id.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ Id.prototype[fl.chain] = function(f) {
return f(this.value);
};

// ChainRec
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);
};
Id.prototype[fl.chainRec] = Id[fl.chainRec];

// Extend
Id.prototype[fl.extend] = function(f) {
return new Id(f(this));
Expand All @@ -66,4 +78,4 @@ Id.prototype[fl.extract] = function() {
return this.value;
};

module.exports = Id;
module.exports = Id;
37 changes: 24 additions & 13 deletions id_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const applicative = require('./laws/applicative');
const apply = require('./laws/apply');
const chain = require('./laws/chain');
const chainRec = require('./laws/chainrec');
const comonad = require('./laws/comonad');
const extend = require('./laws/extend');
const foldable = require('./laws/foldable');
Expand Down Expand Up @@ -40,60 +41,70 @@ const test = f => t => {
t.done();
};

exports.applicative = {
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 = {
exports.apply = {
composition: test((x) => apply.composition(Id)(equality)(x))
};

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

exports.comonad = {
exports.chainRec = {
equivalence: test((x) => {
var predicate = a => a.length > 5
var done = Id.of
var next = a => Id.of(a.concat([x]))
var initial = [x]
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))
};

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

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

exports.functor = {
exports.functor = {
identity: test((x) => functor.identity(Id.of)(equality)(x)),
composition: test((x) => functor.composition(Id.of)(equality)(x))
};

exports.monad = {
exports.monad = {
leftIdentity: test((x) => monad.leftIdentity(Id)(equality)(x)),
rightIdentity: test((x) => monad.rightIdentity(Id)(equality)(x))
};

exports.monoid = {
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 = {
exports.semigroup = {
associativity: test((x) => semigroup.associativity(Id.of)(equality)(x))
};

exports.setoid = {
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 = {
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))))
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
reduce: 'reduce',
sequence: 'sequence',
chain: 'chain',
chainRec: 'chainRec',
extend: 'extend',
extract: 'extract',
bimap: 'bimap',
Expand Down
21 changes: 21 additions & 0 deletions laws/chainrec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

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

/**

### ChainRec

1. `t.chainRec((next, done, v) => p(v) ? d(v).map(done) : n(v).map(next), i)`
is equivalent to
`(function step(v) { return p(v) ? d(v) : n(v).chain(step); }(i))`
(equivalence)
**/

const equivalence = t => eq => p => d => n => x => {
const a = t.chainRec((next, done, v) => p(v) ? d(v).map(done) : n(v).map(next), x);
const b = (function step(v) { return p(v) ? d(v) : n(v).chain(step); }(x));
return eq(a, b);
};

module.exports = { equivalence };