Repository: gcanti/fp-ts
Pull request #1717 (2022-06-06 16:54:16)

Alternative: add getAlternativeMonoid
This provides a way to lift a semigroup into a monoid via an alternative...
think of it like a generalised version of `Option`'s `getMonoid`, except it applies to any `Alternative`.

It also means any module without a specialised `getMonoid` function like `TaskOption` and `IOOption` also have a way to produce an amalgamation.

Hopefully this is useful and not a duplication of functionality.
Alternatively, should all Alternative-capable modules expose a `getMonoid` function?

I did debate adding a way to get an Alternative Semigroup from a Semigroup (and Alternative Monoid from a Monoid) but I realised this had the biggest bang for buck in utilising the capabilities of the underlying Alternative.
I am pretty sure the order of `() => F.alt(first, () => second))` is irrelevant given it's a Semigroup.

(If the tests are redundant I'd be happy to rewrite them as showing identical behaviour to `Option`'s `getMonoid` function.)



gcanti: @willheslam not sure why the CI is failing, the log is no longer available
willheslam: > @willheslam not sure why the CI is failing, the log is no longer available

I don't remember either, but it's passing now. Thanks for reviewing this!

Patch:
diff --git a/docs/modules/Alternative.ts.md b/docs/modules/Alternative.ts.md
index 1efd9c7f..1f109a2d 100644
--- a/docs/modules/Alternative.ts.md
+++ b/docs/modules/Alternative.ts.md
@@ -35,6 +35,7 @@ Added in v2.0.0
   - [Alternative4 (interface)](#alternative4-interface)
 - [utils](#utils)
   - [altAll](#altall)
+  - [getAlternativeMonoid](#getalternativemonoid)
 
 ---
 
@@ -137,3 +138,33 @@ export declare function altAll<F>(F: Alternative<F>): <A>(as: ReadonlyArray<HKT<
 ```
 
 Added in v2.11.0
+
+## getAlternativeMonoid
+
+Lift a semigroup into a monoid alternative 'F', the inner values are concatenated using the provided `Semigroup`.
+
+**Signature**
+
+```ts
+export declare function getAlternativeMonoid<F extends URIS4>(
+  F: Alternative4<F>
+): <A, S, R, E>(S: Semigroup<A>) => Monoid<Kind4<F, S, R, E, A>>
+export declare function getAlternativeMonoid<F extends URIS3>(
+  F: Alternative3<F>
+): <A, R, E>(S: Semigroup<A>) => Monoid<Kind3<F, R, E, A>>
+export declare function getAlternativeMonoid<F extends URIS3, E>(
+  F: Alternative3C<F, E>
+): <A, R>(S: Semigroup<A>) => Monoid<Kind3<F, R, E, A>>
+export declare function getAlternativeMonoid<F extends URIS2>(
+  F: Alternative2<F>
+): <A, E>(S: Semigroup<A>) => Monoid<Kind2<F, E, A>>
+export declare function getAlternativeMonoid<F extends URIS2, E>(
+  F: Alternative2C<F, E>
+): <A>(S: Semigroup<A>) => Monoid<Kind2<F, E, A>>
+export declare function getAlternativeMonoid<F extends URIS>(
+  F: Alternative1<F>
+): <A>(S: Semigroup<A>) => Monoid<Kind<F, A>>
+export declare function getAlternativeMonoid<F>(F: Alternative<F>): <A>(S: Semigroup<A>) => Monoid<HKT<F, A>>
+```
+
+Added in v2.13.0
diff --git a/src/Alternative.ts b/src/Alternative.ts
index 69d15187..384ea72f 100644
--- a/src/Alternative.ts
+++ b/src/Alternative.ts
@@ -24,8 +24,11 @@ import {
   Applicative3C,
   Applicative4
 } from './Applicative'
+import { getApplySemigroup } from './Apply'
 import { HKT, Kind, Kind2, Kind3, Kind4, URIS, URIS2, URIS3, URIS4 } from './HKT'
 import { Zero, Zero1, Zero2, Zero2C, Zero3, Zero3C, Zero4 } from './Zero'
+import { Monoid } from './Monoid'
+import { Semigroup } from './Semigroup'
 
 // -------------------------------------------------------------------------------------
 // model
@@ -98,3 +101,36 @@ export function altAll<F>(F: Alternative<F>): <A>(as: ReadonlyArray<HKT<F, A>>)
 export function altAll<F>(F: Alternative<F>): <A>(as: ReadonlyArray<HKT<F, A>>) => HKT<F, A> {
   return altAll_(F)(F.zero())
 }
+
+/**
+ * Lift a semigroup into a monoid alternative 'F', the inner values are concatenated using the provided `Semigroup`.
+ * @since 2.13.0
+ */
+export function getAlternativeMonoid<F extends URIS4>(
+  F: Alternative4<F>
+): <A, S, R, E>(S: Semigroup<A>) => Monoid<Kind4<F, S, R, E, A>>
+export function getAlternativeMonoid<F extends URIS3>(
+  F: Alternative3<F>
+): <A, R, E>(S: Semigroup<A>) => Monoid<Kind3<F, R, E, A>>
+export function getAlternativeMonoid<F extends URIS3, E>(
+  F: Alternative3C<F, E>
+): <A, R>(S: Semigroup<A>) => Monoid<Kind3<F, R, E, A>>
+export function getAlternativeMonoid<F extends URIS2>(
+  F: Alternative2<F>
+): <A, E>(S: Semigroup<A>) => Monoid<Kind2<F, E, A>>
+export function getAlternativeMonoid<F extends URIS2, E>(
+  F: Alternative2C<F, E>
+): <A>(S: Semigroup<A>) => Monoid<Kind2<F, E, A>>
+export function getAlternativeMonoid<F extends URIS>(F: Alternative1<F>): <A>(S: Semigroup<A>) => Monoid<Kind<F, A>>
+export function getAlternativeMonoid<F>(F: Alternative<F>): <A>(S: Semigroup<A>) => Monoid<HKT<F, A>>
+export function getAlternativeMonoid<F>(F: Alternative<F>): <A>(S: Semigroup<A>) => Monoid<HKT<F, A>> {
+  const f = getApplySemigroup(F)
+  return <A>(S: Semigroup<A>) => {
+    const SF = f(S)
+    return {
+      concat: (first: HKT<F, A>, second: HKT<F, A>) =>
+        F.alt(SF.concat(first, second), () => F.alt(first, () => second)),
+      empty: F.zero()
+    }
+  }
+}


Test patch:
diff --git a/test/Alternative.ts b/test/Alternative.ts
index 6548595f..030103ca 100644
--- a/test/Alternative.ts
+++ b/test/Alternative.ts
@@ -1,5 +1,8 @@
 import * as _ from '../src/Alternative'
+import * as M from '../src/Monoid'
+import * as NEA from '../src/NonEmptyArray'
 import * as O from '../src/Option'
+import * as S from '../src/Semigroup'
 import * as U from './util'
 
 describe('Alternative', () => {
@@ -9,4 +12,21 @@ describe('Alternative', () => {
     U.deepStrictEqual(altAll([O.none]), O.none)
     U.deepStrictEqual(altAll([O.none, O.some(1)]), O.some(1))
   })
+
+  it('getAlternativeMonoid', () => {
+    const altConcatAll = M.concatAll(_.getAlternativeMonoid(O.Alternative)(NEA.getSemigroup<number>()))
+    U.deepStrictEqual(altConcatAll([]), O.none)
+    U.deepStrictEqual(altConcatAll([O.none]), O.none)
+    U.deepStrictEqual(altConcatAll([O.none, O.some([1]), O.some([2])]), O.some([1, 2]))
+
+    const pickFirst = _.getAlternativeMonoid(O.Alternative)(S.first<string>())
+    U.deepStrictEqual(pickFirst.concat(O.some('a'), O.some('b')), O.some('a'))
+    U.deepStrictEqual(pickFirst.concat(O.none, O.some('b')), O.some('b'))
+    U.deepStrictEqual(pickFirst.concat(O.some('a'), O.none), O.some('a'))
+
+    const pickLast = _.getAlternativeMonoid(O.Alternative)(S.last<string>())
+    U.deepStrictEqual(pickLast.concat(O.some('a'), O.some('b')), O.some('b'))
+    U.deepStrictEqual(pickLast.concat(O.none, O.some('b')), O.some('b'))
+    U.deepStrictEqual(pickLast.concat(O.some('a'), O.none), O.some('a'))
+  })
 })