Repository: gcanti/fp-ts
Pull request #1052 (2019-12-16 09:18:16)

add Ordering#monoidOrdering + deprecate Ordering#semigroupOrdering, Ord#getSemigroup


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ec1584d..15ee8616 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,19 @@
 **Note**: Gaps between patch versions are faulty/broken releases. **Note**: A feature tagged as Experimental is in a
 high state of flux, you're at risk of it changing without notice.
 
+# 2.4.0
+
+- **New Feature**
+  - `Ord`
+    - add `getMonoid` (@vicrac)
+  - `Ordering`
+    - add `monoidOrdering` (@gcanti)
+- **Deprecation**
+  - `Ord`
+    - deprecate `getSemigroup` in favor of `getMonoid` (@gcanti)
+  - `Ordering`
+    - deprecate `semigroupOrdering` in favor of `monoidOrdering` (@gcanti)
+
 # 2.3.1
 
 - **Bug Fix**
diff --git a/docs/modules/Ord.ts.md b/docs/modules/Ord.ts.md
index 897fd311..65ba7546 100644
--- a/docs/modules/Ord.ts.md
+++ b/docs/modules/Ord.ts.md
@@ -35,7 +35,8 @@ Added in v2.0.0
 - [fromCompare (function)](#fromcompare-function)
 - [geq (function)](#geq-function)
 - [getDualOrd (function)](#getdualord-function)
-- [getSemigroup (function)](#getsemigroup-function)
+- [getMonoid (function)](#getmonoid-function)
+- [~~getSemigroup~~ (function)](#getsemigroup-function)
 - [getTupleOrd (function)](#gettupleord-function)
 - [gt (function)](#gt-function)
 - [leq (function)](#leq-function)
@@ -184,23 +185,26 @@ export function getDualOrd<A>(O: Ord<A>): Ord<A> { ... }
 
 Added in v2.0.0
 
-# getSemigroup (function)
+# getMonoid (function)
 
-Returns an `Ord` such that its `concat(ord1, ord2)` operation will order first by `ord1`, and then by `ord2`.
+Returns a `Monoid` such that:
+
+- its `concat(ord1, ord2)` operation will order first by `ord1`, and then by `ord2`
+- its `empty` value is an `Ord` that always considers compared elements equal
 
 **Signature**
 
 ```ts
-export function getSemigroup<A = never>(): Semigroup<Ord<A>> { ... }
+export function getMonoid<A = never>(): Monoid<Ord<A>> { ... }
 ```
 
 **Example**
 
 ```ts
 import { sort } from 'fp-ts/lib/Array'
-import { contramap, getDualOrd, getSemigroup, ordBoolean, ordNumber, ordString } from 'fp-ts/lib/Ord'
+import { contramap, getDualOrd, getMonoid, ordBoolean, ordNumber, ordString } from 'fp-ts/lib/Ord'
 import { pipe } from 'fp-ts/lib/pipeable'
-import { fold } from 'fp-ts/lib/Semigroup'
+import { fold } from 'fp-ts/lib/Monoid'
 
 interface User {
   id: number
@@ -224,7 +228,7 @@ const byRememberMe = pipe(
   contramap((p: User) => p.rememberMe)
 )
 
-const S = getSemigroup<User>()
+const M = getMonoid<User>()
 
 const users: Array<User> = [
   { id: 1, name: 'Guido', age: 47, rememberMe: false },
@@ -234,7 +238,7 @@ const users: Array<User> = [
 ]
 
 // sort by name, then by age, then by `rememberMe`
-const O1 = fold(S)(byName, [byAge, byRememberMe])
+const O1 = fold(M)([byName, byAge, byRememberMe])
 assert.deepStrictEqual(sort(O1)(users), [
   { id: 3, name: 'Giulio', age: 44, rememberMe: false },
   { id: 4, name: 'Giulio', age: 44, rememberMe: true },
@@ -243,7 +247,7 @@ assert.deepStrictEqual(sort(O1)(users), [
 ])
 
 // now `rememberMe = true` first, then by name, then by age
-const O2 = fold(S)(getDualOrd(byRememberMe), [byName, byAge])
+const O2 = fold(M)([getDualOrd(byRememberMe), byName, byAge])
 assert.deepStrictEqual(sort(O2)(users), [
   { id: 4, name: 'Giulio', age: 44, rememberMe: true },
   { id: 2, name: 'Guido', age: 46, rememberMe: true },
@@ -252,6 +256,18 @@ assert.deepStrictEqual(sort(O2)(users), [
 ])
 ```
 
+Added in v2.4.0
+
+# ~~getSemigroup~~ (function)
+
+Use `getMonoid` instead
+
+**Signature**
+
+```ts
+export function getSemigroup<A = never>(): Semigroup<Ord<A>> { ... }
+```
+
 Added in v2.0.0
 
 # getTupleOrd (function)
diff --git a/docs/modules/Ordering.ts.md b/docs/modules/Ordering.ts.md
index 42081044..90c7afac 100644
--- a/docs/modules/Ordering.ts.md
+++ b/docs/modules/Ordering.ts.md
@@ -14,7 +14,8 @@ Added in v2.0.0
 
 - [Ordering (type alias)](#ordering-type-alias)
 - [eqOrdering (constant)](#eqordering-constant)
-- [semigroupOrdering (constant)](#semigroupordering-constant)
+- [monoidOrdering (constant)](#monoidordering-constant)
+- [~~semigroupOrdering~~ (constant)](#semigroupordering-constant)
 - [invert (function)](#invert-function)
 - [sign (function)](#sign-function)
 
@@ -40,7 +41,19 @@ export const eqOrdering: Eq<Ordering> = ...
 
 Added in v2.0.0
 
-# semigroupOrdering (constant)
+# monoidOrdering (constant)
+
+**Signature**
+
+```ts
+export const monoidOrdering: Monoid<Ordering> = ...
+```
+
+Added in v2.4.0
+
+# ~~semigroupOrdering~~ (constant)
+
+Use `monoidOrdering` instead
 
 **Signature**
 
diff --git a/package.json b/package.json
index 7f29bb4d..a5694edc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "fp-ts",
-  "version": "2.3.1",
+  "version": "2.4.0",
   "description": "Functional programming in TypeScript",
   "files": [
     "lib",
@@ -11,7 +11,7 @@
   "typings": "lib/index.d.ts",
   "sideEffects": false,
   "scripts": {
-    "lint": "tslint -p tsconfig.tslint.json src/**/*.ts test/**/*.ts",
+    "lint": "tslint -p tsconfig.tslint.json",
     "jest-clear-cache": "jest --clearCache",
     "jest": "jest --ci",
     "prettier": "prettier --list-different \"./{src,test,tutorials}/**/*.ts\"",
diff --git a/src/Array.ts b/src/Array.ts
index 956b69f9..655ba15b 100644
--- a/src/Array.ts
+++ b/src/Array.ts
@@ -19,7 +19,7 @@ import { Monad1 } from './Monad'
 import { Monoid } from './Monoid'
 import { NonEmptyArray } from './NonEmptyArray'
 import { isSome, none, Option, some } from './Option'
-import { fromCompare, getSemigroup, Ord, ordNumber } from './Ord'
+import { fromCompare, getMonoid as getOrdMonoid, Ord, ordNumber } from './Ord'
 import { pipeable } from './pipeable'
 import { Show } from './Show'
 import { TraversableWithIndex1 } from './TraversableWithIndex'
@@ -1107,7 +1107,8 @@ export function uniq<A>(E: Eq<A>): (as: Array<A>) => Array<A> {
  * @since 2.0.0
  */
 export function sortBy<A>(ords: Array<Ord<A>>): (as: Array<A>) => Array<A> {
-  return ords.length > 0 ? sort(ords.slice(1).reduce(getSemigroup<A>().concat, ords[0])) : identity
+  const M = getOrdMonoid<A>()
+  return sort(ords.reduce(M.concat, M.empty))
 }
 
 /**
diff --git a/src/Ord.ts b/src/Ord.ts
index 3a24dcbe..0f94a0a3 100644
--- a/src/Ord.ts
+++ b/src/Ord.ts
@@ -11,7 +11,7 @@
  *
  * @since 2.0.0
  */
-import { Ordering, semigroupOrdering } from './Ordering'
+import { Ordering, monoidOrdering } from './Ordering'
 import { Semigroup } from './Semigroup'
 import { Eq } from './Eq'
 import { Contravariant1 } from './Contravariant'
@@ -161,14 +161,37 @@ export function fromCompare<A>(compare: (x: A, y: A) => Ordering): Ord<A> {
   }
 }
 
+const S: Semigroup<Ord<any>> = {
+  concat: (x, y) => fromCompare((a, b) => monoidOrdering.concat(x.compare(a, b), y.compare(a, b)))
+}
+
+/**
+ * Use `getMonoid` instead
+ *
+ * @since 2.0.0
+ * @deprecated
+ */
+export function getSemigroup<A = never>(): Semigroup<Ord<A>> {
+  return S
+}
+
+const M = {
+  // tslint:disable-next-line: deprecation
+  concat: getSemigroup<any>().concat,
+  empty: fromCompare(() => 0)
+}
+
 /**
- * Returns an `Ord` such that its `concat(ord1, ord2)` operation will order first by `ord1`, and then by `ord2`.
+ * Returns a `Monoid` such that:
+ *
+ * - its `concat(ord1, ord2)` operation will order first by `ord1`, and then by `ord2`
+ * - its `empty` value is an `Ord` that always considers compared elements equal
  *
  * @example
  * import { sort } from 'fp-ts/lib/Array'
- * import { contramap, getDualOrd, getSemigroup, ordBoolean, ordNumber, ordString } from 'fp-ts/lib/Ord'
+ * import { contramap, getDualOrd, getMonoid, ordBoolean, ordNumber, ordString } from 'fp-ts/lib/Ord'
  * import { pipe } from 'fp-ts/lib/pipeable'
- * import { fold } from 'fp-ts/lib/Semigroup'
+ * import { fold } from 'fp-ts/lib/Monoid'
  *
  * interface User {
  *   id: number
@@ -192,7 +215,7 @@ export function fromCompare<A>(compare: (x: A, y: A) => Ordering): Ord<A> {
  *   contramap((p: User) => p.rememberMe)
  * )
  *
- * const S = getSemigroup<User>()
+ * const M = getMonoid<User>()
  *
  * const users: Array<User> = [
  *   { id: 1, name: 'Guido', age: 47, rememberMe: false },
@@ -202,7 +225,7 @@ export function fromCompare<A>(compare: (x: A, y: A) => Ordering): Ord<A> {
  * ]
  *
  * // sort by name, then by age, then by `rememberMe`
- * const O1 = fold(S)(byName, [byAge, byRememberMe])
+ * const O1 = fold(M)([byName, byAge, byRememberMe])
  * assert.deepStrictEqual(sort(O1)(users), [
  *   { id: 3, name: 'Giulio', age: 44, rememberMe: false },
  *   { id: 4, name: 'Giulio', age: 44, rememberMe: true },
@@ -211,7 +234,7 @@ export function fromCompare<A>(compare: (x: A, y: A) => Ordering): Ord<A> {
  * ])
  *
  * // now `rememberMe = true` first, then by name, then by age
- * const O2 = fold(S)(getDualOrd(byRememberMe), [byName, byAge])
+ * const O2 = fold(M)([getDualOrd(byRememberMe), byName, byAge])
  * assert.deepStrictEqual(sort(O2)(users), [
  *   { id: 4, name: 'Giulio', age: 44, rememberMe: true },
  *   { id: 2, name: 'Guido', age: 46, rememberMe: true },
@@ -219,24 +242,10 @@ export function fromCompare<A>(compare: (x: A, y: A) => Ordering): Ord<A> {
  *   { id: 1, name: 'Guido', age: 47, rememberMe: false }
  * ])
  *
- * @since 2.0.0
- */
-export function getSemigroup<A = never>(): Semigroup<Ord<A>> {
-  return {
-    concat: (x, y) => fromCompare((a, b) => semigroupOrdering.concat(x.compare(a, b), y.compare(a, b)))
-  }
-}
-
-const empty = fromCompare(() => 0)
-
-/**
  * @since 2.4.0
  */
 export function getMonoid<A = never>(): Monoid<Ord<A>> {
-  return {
-    empty,
-    concat: getSemigroup<A>().concat
-  }
+  return M
 }
 
 /**
diff --git a/src/Ordering.ts b/src/Ordering.ts
index 9496a5f5..72b1c892 100644
--- a/src/Ordering.ts
+++ b/src/Ordering.ts
@@ -3,6 +3,7 @@
  */
 import { Semigroup } from './Semigroup'
 import { Eq } from './Eq'
+import { Monoid } from './Monoid'
 
 /**
  * @since 2.0.0
@@ -24,12 +25,23 @@ export const eqOrdering: Eq<Ordering> = {
 }
 
 /**
+ * Use `monoidOrdering` instead
  * @since 2.0.0
+ * @deprecated
  */
 export const semigroupOrdering: Semigroup<Ordering> = {
   concat: (x, y) => (x !== 0 ? x : y)
 }
 
+/**
+ * @since 2.4.0
+ */
+export const monoidOrdering: Monoid<Ordering> = {
+  // tslint:disable-next-line: deprecation
+  concat: semigroupOrdering.concat,
+  empty: 0
+}
+
 /**
  * @since 2.0.0
  */


Test patch:
diff --git a/test/Array.ts b/test/Array.ts
index f2c4acd5..1d038fa7 100644
--- a/test/Array.ts
+++ b/test/Array.ts
@@ -579,7 +579,7 @@ describe('Array', () => {
       { name: 'b', age: 3 }
     ])
 
-    assert.equal(sortBy([])(persons), persons)
+    assert.deepStrictEqual(sortBy([])(persons), persons)
   })
 
   it('compact', () => {
diff --git a/test/Ord.ts b/test/Ord.ts
index fe8d4e56..1896e591 100644
--- a/test/Ord.ts
+++ b/test/Ord.ts
@@ -5,7 +5,6 @@ import {
   between,
   clamp,
   getDualOrd,
-  getSemigroup,
   getMonoid,
   ordDate,
   ordNumber,
@@ -26,7 +25,7 @@ describe('Ord', () => {
     assert.strictEqual(O.compare(['a', 1, true], ['a', 1, false]), 1)
   })
 
-  it('getSemigroup', () => {
+  it('getMonoid', () => {
     type T = [number, string]
     const tuples: Array<T> = [
       [2, 'c'],
@@ -34,17 +33,19 @@ describe('Ord', () => {
       [2, 'a'],
       [1, 'c']
     ]
-    const S = getSemigroup<T>()
+    const M = getMonoid<T>()
     const sortByFst = ord.contramap(ordNumber, (x: T) => x[0])
     const sortBySnd = ord.contramap(ordString, (x: T) => x[1])
-    const O1 = S.concat(sortByFst, sortBySnd)
+    //                  v-- left unit
+    const O1 = fold(M)([M.empty, sortByFst, sortBySnd])
     assert.deepStrictEqual(sort(O1)(tuples), [
       [1, 'b'],
       [1, 'c'],
       [2, 'a'],
       [2, 'c']
     ])
-    const O2 = S.concat(sortBySnd, sortByFst)
+    //                           right unit --v
+    const O2 = fold(M)([sortBySnd, sortByFst, M.empty])
     assert.deepStrictEqual(sort(O2)(tuples), [
       [2, 'a'],
       [1, 'b'],
@@ -53,33 +54,6 @@ describe('Ord', () => {
     ])
   })
 
-  it('getMonoid', () => {
-    interface Person {
-      name: string
-      age: number
-    }
-    const people: Person[] = [
-      { name: 'John', age: 42 },
-      { name: 'Dorothy', age: 37 },
-      { name: 'John', age: 37 }
-    ]
-
-    const sortByName = ord.contramap(ordString, (p: Person) => p.name)
-    const sortByAge = ord.contramap(ordNumber, (p: Person) => p.age)
-
-    const monoid = getMonoid<Person>()
-    const sortByNameByAge = fold(monoid)([sortByName, sortByAge])
-    const dontSort = fold(monoid)([])
-
-    assert.deepStrictEqual(sort(sortByNameByAge)(people), [
-      { name: 'Dorothy', age: 37 },
-      { name: 'John', age: 37 },
-      { name: 'John', age: 42 }
-    ])
-
-    assert.deepStrictEqual(sort(dontSort)(people), people)
-  })
-
   it('ordNumber', () => {
     assert.strictEqual(ordNumber.compare(1, 2), -1)
     assert.strictEqual(ordNumber.compare(2, 1), 1)
diff --git a/test/Ordering.ts b/test/Ordering.ts
index ee986296..23cb602d 100644
--- a/test/Ordering.ts
+++ b/test/Ordering.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import { sign, eqOrdering, semigroupOrdering, invert } from '../src/Ordering'
+import { sign, eqOrdering, monoidOrdering, invert } from '../src/Ordering'
 
 describe('Ordering', () => {
   it('sign', () => {
@@ -20,16 +20,25 @@ describe('Ordering', () => {
     assert.strictEqual(eqOrdering.equals(1, 1), true)
   })
 
-  it('semigroupOrdering', () => {
-    assert.strictEqual(semigroupOrdering.concat(-1, -1), -1)
-    assert.strictEqual(semigroupOrdering.concat(-1, 0), -1)
-    assert.strictEqual(semigroupOrdering.concat(-1, 1), -1)
-    assert.strictEqual(semigroupOrdering.concat(0, -1), -1)
-    assert.strictEqual(semigroupOrdering.concat(0, 0), 0)
-    assert.strictEqual(semigroupOrdering.concat(0, 1), 1)
-    assert.strictEqual(semigroupOrdering.concat(1, -1), 1)
-    assert.strictEqual(semigroupOrdering.concat(1, 0), 1)
-    assert.strictEqual(semigroupOrdering.concat(1, 1), 1)
+  it('monoidOrdering', () => {
+    // concat
+    assert.strictEqual(monoidOrdering.concat(-1, -1), -1)
+    assert.strictEqual(monoidOrdering.concat(-1, 0), -1)
+    assert.strictEqual(monoidOrdering.concat(-1, 1), -1)
+    assert.strictEqual(monoidOrdering.concat(0, -1), -1)
+    assert.strictEqual(monoidOrdering.concat(0, 0), 0)
+    assert.strictEqual(monoidOrdering.concat(0, 1), 1)
+    assert.strictEqual(monoidOrdering.concat(1, -1), 1)
+    assert.strictEqual(monoidOrdering.concat(1, 0), 1)
+    assert.strictEqual(monoidOrdering.concat(1, 1), 1)
+
+    // empty
+    assert.strictEqual(monoidOrdering.concat(1, monoidOrdering.empty), 1)
+    assert.strictEqual(monoidOrdering.concat(monoidOrdering.empty, 1), 1)
+    assert.strictEqual(monoidOrdering.concat(-1, monoidOrdering.empty), -1)
+    assert.strictEqual(monoidOrdering.concat(monoidOrdering.empty, -1), -1)
+    assert.strictEqual(monoidOrdering.concat(0, monoidOrdering.empty), 0)
+    assert.strictEqual(monoidOrdering.concat(monoidOrdering.empty, 0), 0)
   })
 
   it('invert', () => {