diff --git a/README.md b/README.md index 06d2772..7186503 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ structures: * [Setoid](#setoid) * [Ord](#ord) +* [Semigroupoid](#semigroupoid) +* [Category](#category) * [Semigroup](#semigroup) * [Monoid](#monoid) * [Functor](#functor) @@ -30,7 +32,7 @@ structures: * [Bifunctor](#bifunctor) * [Profunctor](#profunctor) - + ## General @@ -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) diff --git a/figures/dependencies.dot b/figures/dependencies.dot index 38e84a9..cc5122b 100644 --- a/figures/dependencies.dot +++ b/figures/dependencies.dot @@ -7,6 +7,7 @@ digraph { Applicative; Apply; Bifunctor; + Category; Chain; ChainRec; Comonad; @@ -20,6 +21,7 @@ digraph { Plus; Profunctor; Semigroup; + Semigroupoid; Setoid; Traversable; @@ -41,5 +43,6 @@ digraph { Functor -> Traversable; Plus -> Alternative; Semigroup -> Monoid; + Semigroupoid -> Category; Setoid -> Ord; } diff --git a/figures/dependencies.png b/figures/dependencies.png index 90c2e6c..598c70a 100644 Binary files a/figures/dependencies.png and b/figures/dependencies.png differ diff --git a/index.js b/index.js index 32c8881..14415f3 100644 --- a/index.js +++ b/index.js @@ -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', diff --git a/internal/patch.js b/internal/patch.js index 8a630a9..b7403a1 100644 --- a/internal/patch.js +++ b/internal/patch.js @@ -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; + }; + }; }; diff --git a/laws/category.js b/laws/category.js new file mode 100644 index 0000000..422bdb8 --- /dev/null +++ b/laws/category.js @@ -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}; diff --git a/laws/semigroupoid.js b/laws/semigroupoid.js new file mode 100644 index 0000000..325b109 --- /dev/null +++ b/laws/semigroupoid.js @@ -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}; diff --git a/test.js b/test.js index f5a70c9..0b3fb8a 100644 --- a/test.js +++ b/test.js @@ -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'); @@ -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)), }; @@ -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)),