Repository: gcanti/fp-ts
Pull request #515 (2018-07-02 12:23:14)

Add some semigroups / monoids to Option / Either


Patch:
diff --git a/docs-generator/code2comment.html b/docs-generator/code2comment.html
index a9b217f1..dde51031 100644
--- a/docs-generator/code2comment.html
+++ b/docs-generator/code2comment.html
@@ -21,7 +21,7 @@
     }
     function t(src) {
       const lines = src.split('\n').map(line => '* ' + line.replace('../src/', 'fp-ts/lib/'))
-      return '*\n* @example\n*\n* ' + lines.join('\n') + '\n*'
+      return '*\n* @example\n' + lines.join('\n') + '\n'
     }
   </script>
 </body>
diff --git a/src/Either.ts b/src/Either.ts
index ca58eddb..f96d4e38 100644
--- a/src/Either.ts
+++ b/src/Either.ts
@@ -15,6 +15,7 @@ import { Compactable2C, Separated } from './Compactable'
 import { Monoid } from './Monoid'
 import { Filterable2C } from './Filterable'
 import { Witherable2C } from './Witherable'
+import { Semigroup } from './Semigroup'
 
 declare module './HKT' {
   interface URI2HKT2<L, A> {
@@ -265,6 +266,60 @@ export const getSetoid = <L, A>(SL: Setoid<L>, SA: Setoid<A>): Setoid<Either<L,
   }
 }
 
+/**
+ * Semigroup returning the left-most non-`Left` value. If both operands are `Right`s then the inner values are
+ * appended using the provided `Semigroup`
+ *
+ * @example
+ * import { semigroupSum } from 'fp-ts/lib/Semigroup'
+ *
+ * const S = getSemigroup<string, number>(semigroupSum)
+ * assert.deepEqual(S.concat(left('a'), left('b')), left('a'))
+ * assert.deepEqual(S.concat(left('a'), right(2)), right(2))
+ * assert.deepEqual(S.concat(right(1), left('b')), right(1))
+ * assert.deepEqual(S.concat(right(1), right(2)), right(3))
+ *
+ * @function
+ * @since 1.7.0
+ */
+export const getSemigroup = <L, A>(S: Semigroup<A>): Semigroup<Either<L, A>> => {
+  return {
+    concat: (x, y) => (y.isLeft() ? x : x.isLeft() ? y : right(S.concat(x.value, y.value)))
+  }
+}
+
+/**
+ * {@link Apply} semigroup
+ *
+ * @example
+ * import { semigroupSum } from 'fp-ts/lib/Semigroup'
+ *
+ * const S = getApplySemigroup<string, number>(semigroupSum)
+ * assert.deepEqual(S.concat(left('a'), left('b')), left('a'))
+ * assert.deepEqual(S.concat(left('a'), right(2)), left('a'))
+ * assert.deepEqual(S.concat(right(1), left('b')), left('b'))
+ * assert.deepEqual(S.concat(right(1), right(2)), right(3))
+ *
+ * @function
+ * @since 1.7.0
+ */
+export const getApplySemigroup = <L, A>(S: Semigroup<A>): Semigroup<Either<L, A>> => {
+  return {
+    concat: (x, y) => (x.isLeft() ? x : y.isLeft() ? y : right(S.concat(x.value, y.value)))
+  }
+}
+
+/**
+ * @function
+ * @since 1.7.0
+ */
+export const getApplyMonoid = <L, A>(M: Monoid<A>): Monoid<Either<L, A>> => {
+  return {
+    ...getApplySemigroup(M),
+    empty: right(M.empty)
+  }
+}
+
 const map = <L, A, B>(fa: Either<L, A>, f: (a: A) => B): Either<L, B> => {
   return fa.map(f)
 }
diff --git a/src/Option.ts b/src/Option.ts
index 36cd4333..d1e4b38c 100644
--- a/src/Option.ts
+++ b/src/Option.ts
@@ -402,7 +402,47 @@ const zero = <A>(): Option<A> => {
 }
 
 /**
- * `Option` monoid returning the left-most non-`None` value
+ * {@link Apply} semigroup
+ *
+ * @example
+ * import { semigroupSum } from 'fp-ts/lib/Semigroup'
+ *
+ * const S = getSemigroup(semigroupSum)
+ * assert.deepEqual(S.concat(none, none), none)
+ * assert.deepEqual(S.concat(some(1), none), none)
+ * assert.deepEqual(S.concat(none, some(1)), none)
+ * assert.deepEqual(S.concat(some(1), some(2)), some(3))
+ *
+ * @function
+ * @since 1.7.0
+ */
+export const getApplySemigroup = <A>(S: Semigroup<A>): Semigroup<Option<A>> => {
+  return {
+    concat: (x, y) => (x.isSome() && y.isSome() ? some(S.concat(x.value, y.value)) : none)
+  }
+}
+
+/**
+ * @function
+ * @since 1.7.0
+ */
+export const getApplyMonoid = <A>(M: Monoid<A>): Monoid<Option<A>> => {
+  return {
+    ...getApplySemigroup(M),
+    empty: some(M.empty)
+  }
+}
+
+/**
+ * Monoid returning the left-most non-`None` value
+ *
+ * @example
+ * const M = getFirstMonoid<number>()
+ * assert.deepEqual(M.concat(none, none), none)
+ * assert.deepEqual(M.concat(some(1), none), some(1))
+ * assert.deepEqual(M.concat(none, some(1)), some(1))
+ * assert.deepEqual(M.concat(some(1), some(2)), some(1))
+ *
  * @function
  * @since 1.0.0
  */
@@ -414,7 +454,15 @@ export const getFirstMonoid = <A = never>(): Monoid<Option<A>> => {
 }
 
 /**
- * `Option` monoid returning the right-most non-`None` value
+ * Monoid returning the right-most non-`None` value
+ *
+ * @example
+ * const M = getLastMonoid<number>()
+ * assert.deepEqual(M.concat(none, none), none)
+ * assert.deepEqual(M.concat(some(1), none), some(1))
+ * assert.deepEqual(M.concat(none, some(1)), some(1))
+ * assert.deepEqual(M.concat(some(1), some(2)), some(2))
+ *
  * @function
  * @since 1.0.0
  */
@@ -423,17 +471,16 @@ export const getLastMonoid = <A = never>(): Monoid<Option<A>> => {
 }
 
 /**
- * `Option` monoid returning the left-most non-None value. If both operands are `Some`s then the inner values are
+ * Monoid returning the left-most non-`None` value. If both operands are `Some`s then the inner values are
  * appended using the provided `Semigroup`
  *
  * @example
- * import { none, some, getMonoid } from 'fp-ts/lib/Option'
  * import { semigroupSum } from 'fp-ts/lib/Semigroup'
  *
  * const M = getMonoid(semigroupSum)
  * assert.deepEqual(M.concat(none, none), none)
- * assert.deepEqual(M.concat(none, some(1)), some(1))
  * assert.deepEqual(M.concat(some(1), none), some(1))
+ * assert.deepEqual(M.concat(none, some(1)), some(1))
  * assert.deepEqual(M.concat(some(1), some(2)), some(3))
  *
  * @function


Test patch:
diff --git a/test/Either.ts b/test/Either.ts
index 5608c87e..25112b23 100644
--- a/test/Either.ts
+++ b/test/Either.ts
@@ -16,14 +16,18 @@ import {
   fromRefinement,
   getCompactable,
   getFilterable,
-  getWitherable
+  getWitherable,
+  getApplySemigroup,
+  getApplyMonoid,
+  getSemigroup
 } from '../src/Either'
 import { none, option, some } from '../src/Option'
 import { setoidNumber, setoidString } from '../src/Setoid'
 import { traverse } from '../src/Traversable'
 import { failure, success } from '../src/Validation'
-import { monoidString } from '../src/Monoid'
+import { monoidString, monoidSum } from '../src/Monoid'
 import { Identity, identity as I } from '../src/Identity'
+import { semigroupSum } from '../src/Semigroup'
 
 describe('Either', () => {
   it('fold', () => {
@@ -360,4 +364,28 @@ describe('Either', () => {
       )
     })
   })
+
+  it('getSemigroup', () => {
+    const S = getSemigroup<string, number>(semigroupSum)
+    assert.deepEqual(S.concat(left('a'), left('b')), left('a'))
+    assert.deepEqual(S.concat(left('a'), right(2)), right(2))
+    assert.deepEqual(S.concat(right(1), left('b')), right(1))
+    assert.deepEqual(S.concat(right(1), right(2)), right(3))
+  })
+
+  it('getApplySemigroup', () => {
+    const S = getApplySemigroup<string, number>(semigroupSum)
+    assert.deepEqual(S.concat(left('a'), left('b')), left('a'))
+    assert.deepEqual(S.concat(left('a'), right(2)), left('a'))
+    assert.deepEqual(S.concat(right(1), left('b')), left('b'))
+    assert.deepEqual(S.concat(right(1), right(2)), right(3))
+  })
+
+  it('getApplyMonoid', () => {
+    const S = getApplyMonoid<string, number>(monoidSum)
+    assert.deepEqual(S.concat(left('a'), S.empty), left('a'))
+    assert.deepEqual(S.concat(S.empty, left('b')), left('b'))
+    assert.deepEqual(S.concat(right(1), S.empty), right(1))
+    assert.deepEqual(S.concat(S.empty, right(2)), right(2))
+  })
 })
diff --git a/test/Option.ts b/test/Option.ts
index 6c2b34fe..f721f81b 100644
--- a/test/Option.ts
+++ b/test/Option.ts
@@ -18,14 +18,17 @@ import {
   option,
   some,
   tryCatch,
-  getRefinement
+  getRefinement,
+  getApplySemigroup,
+  getApplyMonoid
 } from '../src/Option'
 import { ordString } from '../src/Ord'
-import { semigroupString } from '../src/Semigroup'
+import { semigroupString, semigroupSum } from '../src/Semigroup'
 import { setoidNumber } from '../src/Setoid'
 import { traverse } from '../src/Traversable'
 import { identity } from '../src/function'
 import { Identity, identity as I } from '../src/Identity'
+import { monoidSum } from '../src/Monoid'
 
 const p = (n: number): boolean => n > 2
 
@@ -200,20 +203,36 @@ describe('Option', () => {
     assert.strictEqual(some(3).reduce(2, (b, a) => b + a), 5)
   })
 
+  it('getApplySemigroup', () => {
+    const S = getApplySemigroup(semigroupSum)
+    assert.deepEqual(S.concat(none, none), none)
+    assert.deepEqual(S.concat(some(1), none), none)
+    assert.deepEqual(S.concat(none, some(1)), none)
+    assert.deepEqual(S.concat(some(1), some(2)), some(3))
+  })
+
+  it('getApplyMonoid', () => {
+    const M = getApplyMonoid(monoidSum)
+    assert.deepEqual(M.concat(M.empty, none), none)
+    assert.deepEqual(M.concat(none, M.empty), none)
+    assert.deepEqual(M.concat(M.empty, some(1)), some(1))
+    assert.deepEqual(M.concat(some(1), M.empty), some(1))
+  })
+
   it('getFirstMonoid', () => {
-    const first = getFirstMonoid<number>()
-    assert.deepEqual(first.concat(none, some(1)), some(1))
-    assert.deepEqual(first.concat(some(1), none), some(1))
-    assert.deepEqual(first.concat(none, none), none)
-    assert.deepEqual(first.concat(some(1), some(2)), some(1))
+    const M = getFirstMonoid<number>()
+    assert.deepEqual(M.concat(none, none), none)
+    assert.deepEqual(M.concat(some(1), none), some(1))
+    assert.deepEqual(M.concat(none, some(1)), some(1))
+    assert.deepEqual(M.concat(some(1), some(2)), some(1))
   })
 
   it('getLastMonoid', () => {
-    const last = getLastMonoid<number>()
-    assert.deepEqual(last.concat(none, some(1)), some(1))
-    assert.deepEqual(last.concat(some(1), none), some(1))
-    assert.deepEqual(last.concat(none, none), none)
-    assert.deepEqual(last.concat(some(1), some(2)), some(2))
+    const M = getLastMonoid<number>()
+    assert.deepEqual(M.concat(none, none), none)
+    assert.deepEqual(M.concat(some(1), none), some(1))
+    assert.deepEqual(M.concat(none, some(1)), some(1))
+    assert.deepEqual(M.concat(some(1), some(2)), some(2))
   })
 
   it('contains', () => {