Skip to content

Commit

Permalink
Merge pull request #248 from gabejohnson/add-semigroupoid-and-category
Browse files Browse the repository at this point in the history
Add Semigroupoid and Category algebras
  • Loading branch information
davidchambers authored May 17, 2017
2 parents 5bcc74d + d82076a commit 3d48842
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 1 deletion.
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ structures:

* [Setoid](#setoid)
* [Ord](#ord)
* [Semigroupoid](#semigroupoid)
* [Category](#category)
* [Semigroup](#semigroup)
* [Monoid](#monoid)
* [Functor](#functor)
Expand All @@ -30,7 +32,7 @@ structures:
* [Bifunctor](#bifunctor)
* [Profunctor](#profunctor)

<img src="figures/dependencies.png" width="888" height="294" />
<img src="figures/dependencies.png" width="888" height="257" />

## General

Expand Down Expand Up @@ -209,6 +211,54 @@ A value which has an Ord must provide a `lte` method. The

2. `lte` must return a boolean (`true` or `false`).

### Semigroupoid

1. `a.compose(b.compose(c)) === a.compose(b).compose(c)` (associativity)

#### `compose` method

```hs
compose :: Semigroupoid c => c i j ~> c j k -> c i k
```

A value which has a Semigroupoid must provide a `compose` method. The
`compose` method takes one argument:

a.compose(b)

1. `b` must be a value of the same Semigroupoid

1. If `b` is not the same semigroupoid, behaviour of `compose` is
unspecified.

2. `compose` must return a value of the same Semigroupoid.

### Category

A value that implements the Category specification must also implement
the [Semigroupoid](#semigroupoid) specification.

1. `a.compose(C.id())` is equivalent to `a` (right identity)
2. `C.id().compose(a)` is equivalent to `a` (left identity)

#### `id` method

```hs
id :: Category c => () -> c a a
```

A value which has a Category must provide an `id` function on its
[type representative](#type-representatives):

C.id()

Given a value `c`, one can access its type representative via the
`constructor` property:

c.constructor.id()

1. `id` must return a value of the same Category

### Semigroup

1. `a.concat(b).concat(c)` is equivalent to `a.concat(b.concat(c))` (associativity)
Expand Down
3 changes: 3 additions & 0 deletions figures/dependencies.dot
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ digraph {
Applicative;
Apply;
Bifunctor;
Category;
Chain;
ChainRec;
Comonad;
Expand All @@ -20,6 +21,7 @@ digraph {
Plus;
Profunctor;
Semigroup;
Semigroupoid;
Setoid;
Traversable;

Expand All @@ -41,5 +43,6 @@ digraph {
Functor -> Traversable;
Plus -> Alternative;
Semigroup -> Monoid;
Semigroupoid -> Category;
Setoid -> Ord;
}
Binary file modified figures/dependencies.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
var mapping = {
equals: 'fantasy-land/equals',
lte: 'fantasy-land/lte',
compose: 'fantasy-land/compose',
id: 'fantasy-land/id',
concat: 'fantasy-land/concat',
empty: 'fantasy-land/empty',
map: 'fantasy-land/map',
Expand Down
12 changes: 12 additions & 0 deletions internal/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,16 @@ module.exports = () => {
return this.concat(b);
};
Array[fl.zero] = () => [];

Function.prototype[fl.compose] = function(g) {
const f = this;
return function(x) {
return f(g(x));
};
};
Function[fl.id] = function() {
return function(x) {
return x;
};
};
};
26 changes: 26 additions & 0 deletions laws/category.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

const {compose, id} = require('..');

/**
### Category
1. `a.compose(C.id())` is equivalent to `a` (right identity)
2. `C.id().compose(a)` is equivalent to `a` (left identity)
**/

const leftIdentity = f => eq => x => {
const a = f[compose](Function[id]())(x);
const b = f(x);
return eq(a, b);
};

const rightIdentity = f => eq => x => {
const a = Function[id]()[compose](f)(x);
const b = f(x);
return eq(a, b);
};

module.exports = {leftIdentity, rightIdentity};
19 changes: 19 additions & 0 deletions laws/semigroupoid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

const {compose} = require('..');

/**
### Semigroupoid
1. `a.compose(b).compose(c)` is equivalent to `a.compose(b.compose(c))` (associativity)
**/

const associativity = f => g => h => eq => x => {
const a = f[compose](g)[compose](h)(x);
const b = f[compose](g[compose](h))(x);
return eq(a, b);
};

module.exports = {associativity};
11 changes: 11 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const monoid = require('./laws/monoid');
const ord = require('./laws/ord');
const plus = require('./laws/plus');
const semigroup = require('./laws/semigroup');
const semigroupoid = require('./laws/semigroupoid');
const category = require('./laws/category');
const setoid = require('./laws/setoid');
const traversable = require('./laws/traversable');

Expand Down Expand Up @@ -51,6 +53,11 @@ exports.apply = {
composition: test(apply.composition(Id)(equality)),
};

exports.category = {
leftIdentity: test(category.leftIdentity(x => x + 1)(equality)),
rightIdentity: test(category.rightIdentity(x => x + 1)(equality)),
};

exports.chain = {
associativity: test(chain.associativity(Id)(equality)),
};
Expand Down Expand Up @@ -109,6 +116,10 @@ exports.semigroup = {
associativity: test(semigroup.associativity(Id[fl.of])(equality)),
};

exports.semigroupoid = {
associativity: semigroupoid.associativity(x => x + 1)(x => x * x)(x => x - 2)(equality)(5),
};

exports.setoid = {
reflexivity: test(setoid.reflexivity(Id[fl.of])(equality)),
symmetry: test(setoid.symmetry(Id[fl.of])(equality)),
Expand Down

0 comments on commit 3d48842

Please sign in to comment.