Skip to content

Commit

Permalink
Merge pull request #137 from scott-christopher/profunctor-bifunctor
Browse files Browse the repository at this point in the history
Profunctor and bifunctor specification
  • Loading branch information
joneshf authored Jul 23, 2016
2 parents 6692bb3 + 1804cf0 commit a6443c9
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 1 deletion.
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ structures:
* [Monad](#monad)
* [Extend](#extend)
* [Comonad](#comonad)
* [Bifunctor](#bifunctor)
* [Profunctor](#profunctor)

<img src="figures/dependencies.png" width="677" height="212" />

Expand Down Expand Up @@ -348,6 +350,61 @@ The `extract` method takes no arguments:
1. `extract` must return a value of type `v`, for some variable `v` contained in `w`.
1. `v` must have the same type that `f` returns in `extend`.

### Bifunctor

A value that implements the Bifunctor specification must also implement
the Functor specification.

1. `p.bimap(a => a, b => b)` is equivalent to `p` (identity)
2. `p.bimap(a => f(g(a)), b => h(i(b))` is equivalent to `p.bimap(g, i).bimap(f, h)` (composition)

#### `bimap` method

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

c.bimap(f, g)

1. `f` must be a function which returns a value

1. If `f` is not a function, the behaviour of `bimap` is unspecified.
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 `bimap` is unspecified.
2. `g` can return any value.

3. `bimap` must return a value of the same Bifunctor.

### Profunctor

A value that implements the Profunctor specification must also implement
the Functor specification.

1. `p.promap(a => a, b => b)` is equivalent to `p` (identity)
2. `p.promap(a => f(g(a)), b => h(i(b)))` is equivalent to `p.promap(f, i).promap(g, h)` (composition)

#### `promap` method

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

The `profunctor` method takes two arguments:

c.promap(f, g)

1. `f` must be a function which returns a value

1. If `f` is not a function, the behaviour of `promap` is unspecified.
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.

3. `promap` must return a value of the same Profunctor

## Derivations

When creating data types which satisfy multiple algebras, authors may choose
Expand All @@ -365,6 +422,18 @@ to implement certain methods then derive the remaining methods. Derivations:
function(f) { var m = this; return m.chain(a => m.of(f(a))); }
```

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

```js
function(f) { return this.bimap(a => a, f); }
```

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

```js
function(f) { return this.promap(a => a, f); }
```

- [`ap`][] may be derived from [`chain`][]:

```js
Expand Down Expand Up @@ -407,6 +476,7 @@ be equivalent to that of the derivation (or derivations).


[`ap`]: #ap-method
[`bimap`]: #bimap-method
[`chain`]: #chain-method
[`concat`]: #concat-method
[`empty`]: #empty-method
Expand All @@ -415,5 +485,6 @@ be equivalent to that of the derivation (or derivations).
[`extract`]: #extract-method
[`map`]: #map-method
[`of`]: #of-method
[`promap`]: #promap-method
[`reduce`]: #reduce-method
[`sequence`]: #sequence-method
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ module.exports = {
sequence: 'sequence',
chain: 'chain',
extend: 'extend',
extract: 'extract'
extract: 'extract',
bimap: 'bimap',
promap: 'promap'
}
28 changes: 28 additions & 0 deletions laws/bifunctor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

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

/**
### Bifunctor
1. `p.bimap(a =>, b => b)` is equivalent to `p` (identity)
2. `p.bimap(compose(f1)(f2), compose(g1)(g2))` is equivalent to `p.bimap(f1, g1).bimap(f2, g2)` (composition)
**/

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

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

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

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

/**
### Profunctor
1. `p.promap(a => a, b => b)` is equivalent to `p` (identity)
2. `p.promap(compose(f1)(f2), compose(g1)(g2))` is equivalent to `p.promap(f1, g1).promap(f2, g2)` (composition)
**/

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

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

module.exports = { identity: identityʹ
, composition
};

0 comments on commit a6443c9

Please sign in to comment.