From 4b4881a3fc9c8ef761edbf39a1838ff54ecb16bb Mon Sep 17 00:00:00 2001 From: Jalil Arfaoui Date: Thu, 20 Jan 2022 22:49:11 +0100 Subject: [PATCH] feat(semigroup): add partial semigroup --- docs/modules/Semigroup.ts.md | 34 ++++++++++++++++++++++++++++++ src/Semigroup.ts | 40 ++++++++++++++++++++++++++++++++++++ test/Semigroup.ts | 9 ++++++++ 3 files changed, 83 insertions(+) diff --git a/docs/modules/Semigroup.ts.md b/docs/modules/Semigroup.ts.md index 787da91ff..55f7f67a9 100644 --- a/docs/modules/Semigroup.ts.md +++ b/docs/modules/Semigroup.ts.md @@ -61,6 +61,7 @@ Added in v2.0.0 - [concatAll](#concatall) - [intercalate](#intercalate) - [reverse](#reverse) + - [partial](#partial) - [struct](#struct) - [tuple](#tuple) - [zone of death](#zone-of-death) @@ -246,6 +247,39 @@ const S1 = pipe(S.Semigroup, intercalate(' + ')) assert.strictEqual(S1.concat('a', 'b'), 'a + b') ``` +Added in v2.14.0 + +## partial + +Given a struct of semigroups returns a semigroup for the struct, that can have optional keys. + +**Signature** + +```ts +export declare const partial: ( + semigroups: { [K in keyof A]: Semigroup } +) => Semigroup<{ readonly [K in keyof Partial]: A[K] }> +``` + +**Example** + +```ts +import { partial, last } from 'fp-ts/Semigroup' +import * as S from 'fp-ts/string' + +interface Person { + readonly name: string + readonly age: number +} + +const S1 = partial({ + name: S.Semigroup, + age: last(), +}) + +assert.deepStrictEqual(S1.concat({ name: 'first', age: 42 }, { name: 'second' }), { name: 'firstsecond', age: 42 }) +``` + Added in v2.10.0 ## reverse diff --git a/src/Semigroup.ts b/src/Semigroup.ts index 9df4f7f58..134a80e2e 100644 --- a/src/Semigroup.ts +++ b/src/Semigroup.ts @@ -156,6 +156,46 @@ export const struct = (semigroups: { [K in keyof A]: Semigroup }): Semi } }) +/** + * Given a struct of semigroups returns a semigroup for the struct, that can have optional keys. + * + * @example + * import { partial, last } from 'fp-ts/Semigroup' + * import * as S from 'fp-ts/string' + * + * interface Person { + * readonly name: string + * readonly age: number + * } + * + * const S1 = partial({ + * name: S.Semigroup, + * age: last() + * }) + * + * assert.deepStrictEqual(S1.concat({ name: "first", age: 42 }, { name: "second" }), { name: "firstsecond", age: 42 }) + * + * @category combinators + * @since 2.10.0 + */ +export const partial = ( + semigroups: { [K in keyof A]: Semigroup } +): Semigroup<{ readonly [K in keyof Partial]: A[K] }> => ({ + concat: (first, second) => { + const r: A = {} as any + for (const k in semigroups) { + if (_.has.call(semigroups, k)) { + if (_.has.call(first, k) && _.has.call(second, k)) { + r[k] = semigroups[k].concat(first[k], second[k]) + } else if (_.has.call(first, k) || _.has.call(second, k)) { + r[k] = second[k] || first[k] + } + } + } + return r + } +}) + /** * Given a tuple of semigroups returns a semigroup for the tuple. * diff --git a/test/Semigroup.ts b/test/Semigroup.ts index ca98eb8c4..fc07ca25e 100644 --- a/test/Semigroup.ts +++ b/test/Semigroup.ts @@ -54,6 +54,15 @@ describe('Semigroup', () => { U.deepStrictEqual(S.concat({}, {}), {}) }) + it('partial', () => { + // should ignore non own properties + const S1 = _.partial(Object.create({ a: 1 })) + U.deepStrictEqual(S1.concat({}, {}), {}) + + const S2 = _.partial({ a: S.Semigroup, b: N.SemigroupSum, c: B.SemigroupAll, d: S.Semigroup }) + U.deepStrictEqual(S2.concat({ a: 'first', b: 12 }, { b: 2, c: true }), { a: 'first', b: 14, c: true }) + }) + it('semigroupAll', () => { const S = _.semigroupAll U.deepStrictEqual(S.concat(true, true), true)