diff --git a/README.md b/README.md index d2215a7..db66371 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ structures: * [Category](#category) * [Semigroup](#semigroup) * [Monoid](#monoid) +* [Group](#group) * [Functor](#functor) * [Contravariant](#contravariant) * [Apply](#apply) @@ -306,6 +307,26 @@ Given a value `m`, one can access its type representative via the 1. `empty` must return a value of the same Monoid +### Group + +A value that implements the Group specification must also implement +the [Monoid](#monoid) specification. + +1. `g.concat(g.invert())` is equivalent to `g.empty()` (right inverse) +2. `g.invert().concat(g)` is equivalent to `g.empty()` (left inverse) + +#### `invert` method + +```hs +invert :: Group g => g ~> () -> g +``` +A value which has a Group must provide an `invert` method. The +`invert` method takes no arguments: + + g.invert() + +1. `invert` must return a value of the same Group. + ### Functor 1. `u.map(a => a)` is equivalent to `u` (identity) diff --git a/figures/dependencies.dot b/figures/dependencies.dot index cc5122b..d632f3f 100644 --- a/figures/dependencies.dot +++ b/figures/dependencies.dot @@ -14,6 +14,7 @@ digraph { Extend; Foldable; Functor; + Group; Contravariant; Monad; Monoid; @@ -41,6 +42,7 @@ digraph { Functor -> Extend; Functor -> Profunctor; Functor -> Traversable; + Monoid -> Group; Plus -> Alternative; Semigroup -> Monoid; Semigroupoid -> Category; diff --git a/figures/dependencies.png b/figures/dependencies.png index 598c70a..e4d814b 100644 Binary files a/figures/dependencies.png and b/figures/dependencies.png differ diff --git a/index.js b/index.js index 14415f3..6e0e13e 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ id: 'fantasy-land/id', concat: 'fantasy-land/concat', empty: 'fantasy-land/empty', + invert: 'fantasy-land/invert', map: 'fantasy-land/map', contramap: 'fantasy-land/contramap', ap: 'fantasy-land/ap', diff --git a/internal/index.js b/internal/index.js index dc8f356..b433c21 100644 --- a/internal/index.js +++ b/internal/index.js @@ -2,7 +2,7 @@ const patch = require('./patch'); const Id = require('./id'); -const Sum = require('./string_sum'); +const Sum = require('./sum'); const Compose = require('./compose'); const {equality} = require('./func'); diff --git a/internal/string_sum.js b/internal/sum.js similarity index 73% rename from internal/string_sum.js rename to internal/sum.js index 9285b35..83053dc 100644 --- a/internal/string_sum.js +++ b/internal/sum.js @@ -5,10 +5,10 @@ const {tagged} = require('daggy'); const fl = require('..'); const {equality} = require('./func'); -// Special type of sum for the type of string. const Sum = module.exports = tagged('v'); + Sum[fl.of] = Sum; -Sum[fl.empty] = () => Sum(''); +Sum[fl.empty] = () => Sum(0); Sum.prototype[fl.map] = function(f) { return Sum(f(this.v)); }; @@ -18,3 +18,6 @@ Sum.prototype[fl.concat] = function(x) { Sum.prototype[fl.equals] = function(x) { return equality(this.v, x.v); }; +Sum.prototype[fl.invert] = function() { + return Sum(this.v >= 0 ? -Math.abs(this.v) : Math.abs(this.v)); +}; diff --git a/laws/group.js b/laws/group.js new file mode 100644 index 0000000..9410157 --- /dev/null +++ b/laws/group.js @@ -0,0 +1,30 @@ +'use strict'; + +const {of, empty, concat, invert} = require('..'); + +/** + +### Group + +1. `g.concat(g.invert())` is equivalent to `g.empty()` (right inverse) +2. `g.invert().concat(g)` is equivalent to `g.empty()` (left inverse) + +**/ + +const rightInverse = T => eq => x => { + const g = T[of](x); + + const a = g[concat](g[invert]()); + const b = T[empty](); + return eq(a, b); +}; + +const leftInverse = T => eq => x => { + const g = T[of](x); + + const a = g[invert]()[concat](g); + const b = T[empty](); + return eq(a, b); +}; + +module.exports = {rightInverse, leftInverse}; diff --git a/test.js b/test.js index 0b3fb8a..93d5fd0 100644 --- a/test.js +++ b/test.js @@ -14,6 +14,7 @@ const comonad = require('./laws/comonad'); const extend = require('./laws/extend'); const foldable = require('./laws/foldable'); const functor = require('./laws/functor'); +const group = require('./laws/group'); const monad = require('./laws/monad'); const monoid = require('./laws/monoid'); const ord = require('./laws/ord'); @@ -90,6 +91,11 @@ exports.functor = { composition: test(functor.composition(Id[fl.of])(equality)(a => [a, a])(a => [a])), }; +exports.group = { + rightInverse: test(() => group.rightInverse(Sum[fl.of])(equality)(42)), + leftInverse: test(() => group.leftInverse(Sum[fl.of])(equality)(42)), +}; + exports.monad = { leftIdentity: test(monad.leftIdentity(Id)(equality)(Id[fl.of])), rightIdentity: test(monad.rightIdentity(Id)(equality)), @@ -102,8 +108,8 @@ exports.plus = { }; exports.monoid = { - leftIdentity: test(monoid.leftIdentity(Sum)(equality)), - rightIdentity: test(monoid.rightIdentity(Sum)(equality)), + leftIdentity: test(() => monoid.leftIdentity(Sum)(equality)(23)), + rightIdentity: test(() => monoid.rightIdentity(Sum)(equality)(23)), }; exports.ord = { @@ -117,7 +123,7 @@ exports.semigroup = { }; exports.semigroupoid = { - associativity: semigroupoid.associativity(x => x + 1)(x => x * x)(x => x - 2)(equality)(5), + associativity: test(() => semigroupoid.associativity(x => x + 1)(x => x * x)(x => x - 2)(equality)(5)), }; exports.setoid = { @@ -129,5 +135,5 @@ exports.setoid = { exports.traversable = { naturality: test(x => traversable.naturality(Id)(Id[fl.of])(equality)(Id[fl.of](x))), identity: test(traversable.identity(Id)(equality)), - composition: test(x => traversable.composition(Id)(Id[fl.of])(equality)(Id[fl.of](Sum[fl.of](x)))), + composition: test(() => traversable.composition(Id)(Id[fl.of])(equality)(Id[fl.of](Sum[fl.of](37)))), };