Skip to content

Commit

Permalink
Merge branch 'master' of github.com:fantasyland/fantasy-land
Browse files Browse the repository at this point in the history
* 'master' of github.com:fantasyland/fantasy-land:
  Add ChainRec specification (fantasyland#152)
  Add type signatures to spec (fantasyland#147)
  • Loading branch information
rjmk committed Sep 6, 2016
2 parents 7ba1e0c + b436d6d commit 87cb883
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 16 deletions.
87 changes: 85 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ structures:
* [Foldable](#foldable)
* [Traversable](#traversable)
* [Chain](#chain)
* [ChainRec](#chainrec)
* [Monad](#monad)
* [Extend](#extend)
* [Comonad](#comonad)
Expand Down Expand Up @@ -112,6 +113,10 @@ have dependencies on other algebras which must be implemented.

#### `equals` method

```hs
equals :: Setoid a => a ~> a -> Boolean
```

A value which has a Setoid must provide an `equals` method. The
`equals` method takes one argument:

Expand All @@ -130,6 +135,10 @@ A value which has a Setoid must provide an `equals` method. The

#### `concat` method

```hs
concat :: Semigroup a => a ~> a -> a
```

A value which has a Semigroup must provide a `concat` method. The
`concat` method takes one argument:

Expand All @@ -152,6 +161,10 @@ the Semigroup specification.

#### `empty` method

```hs
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:

Expand All @@ -167,6 +180,10 @@ its `constructor` object. The `empty` method takes no arguments:

#### `map` method

```hs
map :: Functor f => f a ~> (a -> b) -> f b
```

A value which has a Functor must provide a `map` method. The `map`
method takes one argument:

Expand Down Expand Up @@ -210,6 +227,10 @@ implement the Functor specification.

#### `ap` method

```hs
ap :: Apply f => f (a -> b) ~> f a -> f b
```

A value which has an Apply must provide an `ap` method. The `ap`
method takes one argument:

Expand All @@ -236,6 +257,10 @@ implement the Apply specification.

#### `of` method

```hs
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:

Expand All @@ -252,6 +277,10 @@ or its `constructor` object. The `of` method takes one argument:

#### `reduce` method

```hs
reduce :: Foldable f => f a ~> (b -> a -> b) -> b -> b
```

A value which has a Foldable must provide a `reduce` method. The `reduce`
method takes two arguments:

Expand Down Expand Up @@ -298,6 +327,10 @@ Compose.prototype.map = function(f) {

#### `sequence` method

```hs
sequence :: Apply f, Traversable t => t (f a) ~> (b -> f b) -> f (t a)
```

A value which has a Traversable must provide a `sequence` method. The `sequence`
method takes one argument:

Expand All @@ -314,6 +347,10 @@ implement the Apply specification.

#### `chain` method

```hs
chain :: Chain m => m a ~> (a -> m b) -> m b
```

A value which has a Chain must provide a `chain` method. The `chain`
method takes one argument:

Expand All @@ -327,6 +364,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)
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 All @@ -341,6 +408,10 @@ the Applicative and Chain specifications.

#### `extend` method

```hs
extend :: Extend w => w a ~> (w a -> b) -> w b
```

An Extend must provide an `extend` method. The `extend`
method takes one argument:

Expand All @@ -364,6 +435,10 @@ A value that implements the Comonad specification must also implement the Functo

#### `extract` method

```hs
extract :: Comonad w => w a ~> () -> a
```

A value which has a Comonad must provide an `extract` method on itself.
The `extract` method takes no arguments:

Expand All @@ -382,6 +457,10 @@ the Functor specification.

#### `bimap` method

```hs
bimap :: Bifunctor f => f a c ~> (a -> b) -> (c -> d) -> f b d
```

A value which has a Bifunctor must provide an `bimap` method. The `bimap`
method takes two arguments:

Expand Down Expand Up @@ -409,6 +488,10 @@ the Functor and Contravariant Functor specifications.

#### `promap` method

```hs
promap :: Profunctor p => p b c ~> (a -> b) -> (c -> d) -> p a d
```

A value which has a Profunctor must provide a `promap` method.

The `profunctor` method takes two arguments:
Expand All @@ -421,7 +504,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 @@ -445,7 +528,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 @@ -9,6 +9,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 };

0 comments on commit 87cb883

Please sign in to comment.