From 7231e8408493a49e184e935dde2e6627e6a7c098 Mon Sep 17 00:00:00 2001 From: gcanti Date: Fri, 25 Sep 2020 10:00:42 +0200 Subject: [PATCH] Docs: add Monoid examples --- docs/modules/Monoid.ts.md | 185 +++++++++++++++++++++++++++++++++-- src/Monoid.ts | 200 +++++++++++++++++++++++++++++++------- 2 files changed, 343 insertions(+), 42 deletions(-) diff --git a/docs/modules/Monoid.ts.md b/docs/modules/Monoid.ts.md index 1c80e2f5a..3dbbf9dc5 100644 --- a/docs/modules/Monoid.ts.md +++ b/docs/modules/Monoid.ts.md @@ -6,6 +6,37 @@ parent: Modules ## Monoid overview +`Monoid` extends the power of `Semigroup` by providing an additional `empty` value. + +```ts +interface Semigroup { + readonly concat: (x: A, y: A) => A +} + +interface Monoid extends Semigroup { + readonly empty: A +} +``` + +This `empty` value should be an identity for the `concat` operation, which means the following equalities hold for any choice of `x`. + +```ts +concat(x, empty) = concat(empty, x) = x +``` + +Many types that form a `Semigroup` also form a `Monoid`, such as `number`s (with `0`) and `string`s (with `''`). + +```ts +import { Monoid } from 'fp-ts/Monoid' + +const monoidString: Monoid = { + concat: (x, y) => x + y, + empty: '', +} +``` + +_Adapted from https://typelevel.org/cats_ + Added in v2.0.0 --- @@ -60,6 +91,8 @@ Added in v2.0.0 ## getEndomorphismMonoid +Endomorphism form a monoid where the `empty` value is the identity function. + **Signature** ```ts @@ -70,36 +103,90 @@ Added in v2.0.0 ## getFunctionMonoid +Unary functions form a monoid as long as you can provide a monoid for the codomain. + **Signature** ```ts export declare function getFunctionMonoid(M: Monoid): () => Monoid<(a: A) => M> ``` +**Example** + +```ts +import { Predicate } from 'fp-ts/function' +import * as M from 'fp-ts/Monoid' + +const f: Predicate = (n) => n <= 2 +const g: Predicate = (n) => n >= 0 + +const M1 = M.getFunctionMonoid(M.monoidAll)() + +assert.deepStrictEqual(M1.concat(f, g)(1), true) +assert.deepStrictEqual(M1.concat(f, g)(3), false) + +const M2 = M.getFunctionMonoid(M.monoidAny)() + +assert.deepStrictEqual(M2.concat(f, g)(1), true) +assert.deepStrictEqual(M2.concat(f, g)(3), true) +``` + Added in v2.0.0 ## getJoinMonoid +Get a monoid where `concat` will return the maximum, based on the provided bounded order. + +The `empty` value is the `bottom` value. + **Signature** ```ts export declare function getJoinMonoid(B: Bounded): Monoid ``` +**Example** + +```ts +import * as B from 'fp-ts/Bounded' +import * as M from 'fp-ts/Monoid' + +const M1 = M.getJoinMonoid(B.boundedNumber) + +assert.deepStrictEqual(M1.concat(1, 2), 2) +``` + Added in v2.0.0 ## getMeetMonoid +Get a monoid where `concat` will return the minimum, based on the provided bounded order. + +The `empty` value is the `top` value. + **Signature** ```ts export declare function getMeetMonoid(B: Bounded): Monoid ``` +**Example** + +```ts +import * as B from 'fp-ts/Bounded' +import * as M from 'fp-ts/Monoid' + +const M1 = M.getMeetMonoid(B.boundedNumber) + +assert.deepStrictEqual(M1.concat(1, 2), 1) +``` + Added in v2.0.0 ## getStructMonoid +Given a struct of monoids returns a monoid for the struct. + **Signature** ```ts @@ -108,6 +195,24 @@ export declare function getStructMonoid>( ): Monoid ``` +**Example** + +```ts +import * as M from 'fp-ts/Monoid' + +interface Point { + readonly x: number + readonly y: number +} + +const monoidPoint = M.getStructMonoid({ + x: M.monoidSum, + y: M.monoidSum, +}) + +assert.deepStrictEqual(monoidPoint.concat({ x: 1, y: 2 }, { x: 3, y: 4 }), { x: 4, y: 6 }) +``` + Added in v2.0.0 ## getTupleMonoid @@ -119,7 +224,7 @@ Given a tuple of monoids returns a monoid for the tuple ```ts export declare function getTupleMonoid>>( ...monoids: T -): Monoid<{ [K in keyof T]: T[K] extends Semigroup ? A : never }> +): Monoid<{ [K in keyof T]: T[K] extends S.Semigroup ? A : never }> ``` **Example** @@ -138,7 +243,9 @@ Added in v2.0.0 ## monoidAll -Boolean monoid under conjunction +`boolean` monoid under conjunction. + +The `empty` value is `true`. **Signature** @@ -146,11 +253,22 @@ Boolean monoid under conjunction export declare const monoidAll: Monoid ``` +**Example** + +```ts +import * as M from 'fp-ts/Monoid' + +assert.deepStrictEqual(M.monoidAll.concat(true, true), true) +assert.deepStrictEqual(M.monoidAll.concat(true, false), false) +``` + Added in v2.0.0 ## monoidAny -Boolean monoid under disjunction +`boolean` monoid under disjunction. + +The `empty` value is `false`. **Signature** @@ -158,11 +276,23 @@ Boolean monoid under disjunction export declare const monoidAny: Monoid ``` +**Example** + +```ts +import * as M from 'fp-ts/Monoid' + +assert.deepStrictEqual(M.monoidAny.concat(true, true), true) +assert.deepStrictEqual(M.monoidAny.concat(true, false), true) +assert.deepStrictEqual(M.monoidAny.concat(false, false), false) +``` + Added in v2.0.0 ## monoidProduct -Number monoid under multiplication +`number` monoid under multiplication. + +The `empty` value is `1`. **Signature** @@ -170,21 +300,43 @@ Number monoid under multiplication export declare const monoidProduct: Monoid ``` +**Example** + +```ts +import * as M from 'fp-ts/Monoid' + +assert.deepStrictEqual(M.monoidProduct.concat(2, 3), 6) +``` + Added in v2.0.0 ## monoidString +`string` monoid under concatenation. + +The `empty` value is `''`. + **Signature** ```ts export declare const monoidString: Monoid ``` +**Example** + +```ts +import * as M from 'fp-ts/Monoid' + +assert.deepStrictEqual(M.monoidString.concat('a', 'b'), 'ab') +``` + Added in v2.0.0 ## monoidSum -Number monoid under addition +`number` monoid under addition. + +The `empty` value is `0`. **Signature** @@ -192,6 +344,14 @@ Number monoid under addition export declare const monoidSum: Monoid ``` +**Example** + +```ts +import * as M from 'fp-ts/Monoid' + +assert.deepStrictEqual(M.monoidSum.concat(2, 3), 5) +``` + Added in v2.0.0 ## monoidVoid @@ -211,7 +371,7 @@ Added in v2.0.0 **Signature** ```ts -export interface Monoid extends Semigroup { +export interface Monoid extends S.Semigroup { readonly empty: A } ``` @@ -222,10 +382,23 @@ Added in v2.0.0 ## fold +Given a sequence of `as`, concat them and return the total. + +If `as` is empty, return the monoid `empty` value. + **Signature** ```ts export declare function fold(M: Monoid): (as: ReadonlyArray) => A ``` +**Example** + +```ts +import * as M from 'fp-ts/Monoid' + +assert.deepStrictEqual(M.fold(M.monoidSum)([1, 2, 3]), 6) +assert.deepStrictEqual(M.fold(M.monoidSum)([]), 0) +``` + Added in v2.0.0 diff --git a/src/Monoid.ts b/src/Monoid.ts index f0d75315d..adc1e0399 100644 --- a/src/Monoid.ts +++ b/src/Monoid.ts @@ -1,84 +1,140 @@ /** + * `Monoid` extends the power of `Semigroup` by providing an additional `empty` value. + * + * ```ts + * interface Semigroup { + * readonly concat: (x: A, y: A) => A + * } + * + * interface Monoid extends Semigroup { + * readonly empty: A + * } + * ``` + * + * This `empty` value should be an identity for the `concat` operation, which means the following equalities hold for any choice of `x`. + * + * ```ts + * concat(x, empty) = concat(empty, x) = x + * ``` + * + * Many types that form a `Semigroup` also form a `Monoid`, such as `number`s (with `0`) and `string`s (with `''`). + * + * ```ts + * import { Monoid } from 'fp-ts/Monoid' + * + * const monoidString: Monoid = { + * concat: (x, y) => x + y, + * empty: '' + * } + * ``` + * + * *Adapted from https://typelevel.org/cats* + * * @since 2.0.0 */ import { Bounded } from './Bounded' import { Endomorphism, identity } from './function' import { ReadonlyRecord } from './ReadonlyRecord' -import { - fold as foldSemigroup, - getDualSemigroup, - getFunctionSemigroup, - getJoinSemigroup, - getMeetSemigroup, - getStructSemigroup, - getTupleSemigroup, - Semigroup, - semigroupAll, - semigroupAny, - semigroupProduct, - semigroupString, - semigroupSum, - semigroupVoid -} from './Semigroup' +import * as S from './Semigroup' /** * @category type classes * @since 2.0.0 */ -export interface Monoid extends Semigroup { +export interface Monoid extends S.Semigroup { readonly empty: A } /** - * Boolean monoid under conjunction + * `boolean` monoid under conjunction. + * + * The `empty` value is `true`. + * + * @example + * import * as M from 'fp-ts/Monoid' + * + * assert.deepStrictEqual(M.monoidAll.concat(true, true), true) + * assert.deepStrictEqual(M.monoidAll.concat(true, false), false) * * @category instances * @since 2.0.0 */ export const monoidAll: Monoid = { - concat: semigroupAll.concat, + concat: S.semigroupAll.concat, empty: true } /** - * Boolean monoid under disjunction + * `boolean` monoid under disjunction. + * + * The `empty` value is `false`. + * + * @example + * import * as M from 'fp-ts/Monoid' + * + * assert.deepStrictEqual(M.monoidAny.concat(true, true), true) + * assert.deepStrictEqual(M.monoidAny.concat(true, false), true) + * assert.deepStrictEqual(M.monoidAny.concat(false, false), false) * * @category instances * @since 2.0.0 */ export const monoidAny: Monoid = { - concat: semigroupAny.concat, + concat: S.semigroupAny.concat, empty: false } /** - * Number monoid under addition + * `number` monoid under addition. + * + * The `empty` value is `0`. + * + * @example + * import * as M from 'fp-ts/Monoid' + * + * assert.deepStrictEqual(M.monoidSum.concat(2, 3), 5) * * @category instances * @since 2.0.0 */ export const monoidSum: Monoid = { - concat: semigroupSum.concat, + concat: S.semigroupSum.concat, empty: 0 } /** - * Number monoid under multiplication + * `number` monoid under multiplication. + * + * The `empty` value is `1`. + * + * @example + * import * as M from 'fp-ts/Monoid' + * + * assert.deepStrictEqual(M.monoidProduct.concat(2, 3), 6) * * @category instances * @since 2.0.0 */ export const monoidProduct: Monoid = { - concat: semigroupProduct.concat, + concat: S.semigroupProduct.concat, empty: 1 } /** + * `string` monoid under concatenation. + * + * The `empty` value is `''`. + * + * @example + * import * as M from 'fp-ts/Monoid' + * + * assert.deepStrictEqual(M.monoidString.concat('a', 'b'), 'ab') + * * @category instances * @since 2.0.0 */ export const monoidString: Monoid = { - concat: semigroupString.concat, + concat: S.semigroupString.concat, empty: '' } @@ -87,16 +143,25 @@ export const monoidString: Monoid = { * @since 2.0.0 */ export const monoidVoid: Monoid = { - concat: semigroupVoid.concat, + concat: S.semigroupVoid.concat, empty: undefined } /** + * Given a sequence of `as`, concat them and return the total. + * + * If `as` is empty, return the monoid `empty` value. + * + * @example + * import * as M from 'fp-ts/Monoid' + * + * assert.deepStrictEqual(M.fold(M.monoidSum)([1, 2, 3]), 6) + * assert.deepStrictEqual(M.fold(M.monoidSum)([]), 0) + * * @since 2.0.0 */ export function fold(M: Monoid): (as: ReadonlyArray) => A { - const foldM = foldSemigroup(M) - return (as) => foldM(M.empty, as) + return S.fold(M)(M.empty) } /** @@ -116,9 +181,9 @@ export function fold(M: Monoid): (as: ReadonlyArray) => A { */ export function getTupleMonoid>>( ...monoids: T -): Monoid<{ [K in keyof T]: T[K] extends Semigroup ? A : never }> { +): Monoid<{ [K in keyof T]: T[K] extends S.Semigroup ? A : never }> { return { - concat: getTupleSemigroup(...monoids).concat, + concat: S.getTupleSemigroup(...monoids).concat, empty: monoids.map((m) => m.empty) } as any } @@ -136,23 +201,45 @@ export function getTupleMonoid>>( */ export function getDualMonoid(M: Monoid): Monoid { return { - concat: getDualSemigroup(M).concat, + concat: S.getDualSemigroup(M).concat, empty: M.empty } } /** + * Unary functions form a monoid as long as you can provide a monoid for the codomain. + * + * @example + * import { Predicate } from 'fp-ts/function' + * import * as M from 'fp-ts/Monoid' + * + * const f: Predicate = (n) => n <= 2 + * const g: Predicate = (n) => n >= 0 + * + * const M1 = M.getFunctionMonoid(M.monoidAll)() + * + * assert.deepStrictEqual(M1.concat(f, g)(1), true) + * assert.deepStrictEqual(M1.concat(f, g)(3), false) + * + * const M2 = M.getFunctionMonoid(M.monoidAny)() + * + * assert.deepStrictEqual(M2.concat(f, g)(1), true) + * assert.deepStrictEqual(M2.concat(f, g)(3), true) + * * @category instances * @since 2.0.0 */ export function getFunctionMonoid(M: Monoid): () => Monoid<(a: A) => M> { return () => ({ - concat: getFunctionSemigroup(M)().concat, + concat: S.getFunctionSemigroup(M)().concat, empty: () => M.empty }) } +// TODO: swap execution order in v3 /** + * Endomorphism form a monoid where the `empty` value is the identity function. + * * @category instances * @since 2.0.0 */ @@ -164,6 +251,23 @@ export function getEndomorphismMonoid(): Monoid> { } /** + * Given a struct of monoids returns a monoid for the struct. + * + * @example + * import * as M from 'fp-ts/Monoid' + * + * interface Point { + * readonly x: number + * readonly y: number + * } + * + * const monoidPoint = M.getStructMonoid({ + * x: M.monoidSum, + * y: M.monoidSum + * }) + * + * assert.deepStrictEqual(monoidPoint.concat({ x: 1, y: 2 }, { x: 3, y: 4 }), { x: 4, y: 6 }) + * * @category instances * @since 2.0.0 */ @@ -175,29 +279,53 @@ export function getStructMonoid>( empty[key] = monoids[key].empty } return { - concat: getStructSemigroup(monoids).concat, + concat: S.getStructSemigroup(monoids).concat, empty } } /** + * Get a monoid where `concat` will return the minimum, based on the provided bounded order. + * + * The `empty` value is the `top` value. + * + * @example + * import * as B from 'fp-ts/Bounded' + * import * as M from 'fp-ts/Monoid' + * + * const M1 = M.getMeetMonoid(B.boundedNumber) + * + * assert.deepStrictEqual(M1.concat(1, 2), 1) + * * @category instances * @since 2.0.0 */ export function getMeetMonoid(B: Bounded): Monoid { return { - concat: getMeetSemigroup(B).concat, + concat: S.getMeetSemigroup(B).concat, empty: B.top } } /** + * Get a monoid where `concat` will return the maximum, based on the provided bounded order. + * + * The `empty` value is the `bottom` value. + * + * @example + * import * as B from 'fp-ts/Bounded' + * import * as M from 'fp-ts/Monoid' + * + * const M1 = M.getJoinMonoid(B.boundedNumber) + * + * assert.deepStrictEqual(M1.concat(1, 2), 2) + * * @category instances * @since 2.0.0 */ export function getJoinMonoid(B: Bounded): Monoid { return { - concat: getJoinSemigroup(B).concat, + concat: S.getJoinSemigroup(B).concat, empty: B.bottom } }