From 0e6962a3bf974c78965567bd4701f66ddbf2d4c7 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 07:47:40 +0200 Subject: [PATCH 01/12] add more tests --- src/Bounded.ts | 5 +++-- test/Bounded.ts | 20 +++++++++++++++++ test/FlatMap.ts | 36 +++++++++++++++++++++++++++++++ test/Foldable.ts | 13 ++++++++--- test/FoldableWithIndex.ts | 14 +++++++++--- test/Functor.ts | 44 ++++++++++++++++++++++++++++++++++++++ test/MonoidKind.ts | 14 ++++++++++++ test/Ordering.ts | 17 ++++++++++++++- test/Semigroup.ts | 27 ++++++++++++----------- test/Sortable.ts | 24 ++++++++++----------- test/data/Option.ts | 12 +++++++++++ test/data/ReadonlyArray.ts | 4 ++-- test/data/number.ts | 4 ++-- 13 files changed, 197 insertions(+), 37 deletions(-) create mode 100644 test/Bounded.ts create mode 100644 test/FlatMap.ts create mode 100644 test/Functor.ts create mode 100644 test/MonoidKind.ts diff --git a/src/Bounded.ts b/src/Bounded.ts index c69d89ef7..83bd75c4f 100644 --- a/src/Bounded.ts +++ b/src/Bounded.ts @@ -9,8 +9,8 @@ import type { Sortable } from "@fp-ts/core/Sortable" * @since 1.0.0 */ export interface Bounded extends Sortable { - readonly maximum: A readonly minimum: A + readonly maximum: A } /** @@ -28,7 +28,8 @@ export const fromSortable = (Sortable: Sortable, maximum: A, minimum: A): * * @since 1.0.0 */ -export const clamp = (B: Bounded): (a: A) => A => compare.clamp(B)(B.minimum, B.maximum) +export const clamp = (Bounded: Bounded): (a: A) => A => + compare.clamp(Bounded)(Bounded.minimum, Bounded.maximum) /** * Reverses the `Ord` of a `Bounded` and flips `maximum` and `minimum` values. diff --git a/test/Bounded.ts b/test/Bounded.ts new file mode 100644 index 000000000..38b405c44 --- /dev/null +++ b/test/Bounded.ts @@ -0,0 +1,20 @@ +import * as _ from "@fp-ts/core/Bounded" +import * as number from "./data/number" +import * as U from "./util" + +describe("Bounded", () => { + it("clamp", () => { + const clamp = _.clamp(_.fromSortable(number.Sortable, 10, 1)) + U.deepStrictEqual(clamp(2), 2) + U.deepStrictEqual(clamp(10), 10) + U.deepStrictEqual(clamp(20), 10) + U.deepStrictEqual(clamp(1), 1) + U.deepStrictEqual(clamp(-10), 1) + }) + + it("reverse", () => { + const Bounded = _.reverse(_.fromSortable(number.Sortable, 10, 1)) + U.deepStrictEqual(Bounded.maximum, 1) + U.deepStrictEqual(Bounded.minimum, 10) + }) +}) diff --git a/test/FlatMap.ts b/test/FlatMap.ts new file mode 100644 index 000000000..1969bc8f4 --- /dev/null +++ b/test/FlatMap.ts @@ -0,0 +1,36 @@ +import * as _ from "@fp-ts/core/FlatMap" +import { pipe } from "@fp-ts/core/internal/Function" +import * as O from "./data/Option" +import * as U from "./util" + +describe("FlatMap", () => { + it("zipLeft", () => { + const zipLeft = _.zipLeft(O.FlatMap) + U.deepStrictEqual(pipe(O.none, zipLeft(O.none)), O.none) + U.deepStrictEqual(pipe(O.none, zipLeft(O.some(2))), O.none) + U.deepStrictEqual(pipe(O.some(1), zipLeft(O.none)), O.none) + U.deepStrictEqual(pipe(O.some(1), zipLeft(O.some(2))), O.some(1)) + }) + + it("zipRight", () => { + const zipRight = _.zipRight(O.FlatMap) + U.deepStrictEqual(pipe(O.none, zipRight(O.none)), O.none) + U.deepStrictEqual(pipe(O.none, zipRight(O.some(2))), O.none) + U.deepStrictEqual(pipe(O.some(1), zipRight(O.none)), O.none) + U.deepStrictEqual(pipe(O.some(1), zipRight(O.some(2))), O.some(2)) + }) + + it("bind", () => { + const bind = _.bind(O.FlatMap) + U.deepStrictEqual(pipe(O.Do, bind("a", () => O.none)), O.none) + U.deepStrictEqual(pipe(O.Do, bind("a", () => O.some(1))), O.some({ a: 1 })) + }) + + it("tap", () => { + const tap = _.tap(O.FlatMap) + U.deepStrictEqual(pipe(O.none, tap(() => O.none)), O.none) + U.deepStrictEqual(pipe(O.none, tap(() => O.some(2))), O.none) + U.deepStrictEqual(pipe(O.some(1), tap(() => O.none)), O.none) + U.deepStrictEqual(pipe(O.some(1), tap(() => O.some(2))), O.some(1)) + }) +}) diff --git a/test/Foldable.ts b/test/Foldable.ts index 376a22e34..39febe462 100644 --- a/test/Foldable.ts +++ b/test/Foldable.ts @@ -1,12 +1,19 @@ import * as foldable from "@fp-ts/core/Foldable" import { pipe } from "@fp-ts/core/internal/Function" -import * as monoidTests from "./data/number" +import * as number from "./data/number" +import * as O from "./data/Option" import * as RA from "./data/ReadonlyArray" import * as U from "./util" describe("Foldable", () => { + it("toReadonlyArrayWith", () => { + const toReadonlyArrayWith = foldable.toReadonlyArrayWith(O.Foldable) + U.deepStrictEqual(toReadonlyArrayWith(O.none, U.double), []) + U.deepStrictEqual(toReadonlyArrayWith(O.some(2), U.double), [4]) + }) + it("foldMap", () => { - const foldMap = foldable.foldMap(RA.FoldableReadonlyArray) - U.deepStrictEqual(pipe([1, 2, 3], foldMap(monoidTests.MonoidSum)(U.double)), 12) + const foldMap = foldable.foldMap(RA.Foldable) + U.deepStrictEqual(pipe([1, 2, 3], foldMap(number.MonoidSum)(U.double)), 12) }) }) diff --git a/test/FoldableWithIndex.ts b/test/FoldableWithIndex.ts index 11ffe2b8f..9773bc67b 100644 --- a/test/FoldableWithIndex.ts +++ b/test/FoldableWithIndex.ts @@ -1,14 +1,22 @@ import * as foldableWithIndex from "@fp-ts/core/FoldableWithIndex" import { pipe } from "@fp-ts/core/internal/Function" -import * as monoidTests from "./data/number" +import * as number from "./data/number" +import * as O from "./data/Option" import * as RA from "./data/ReadonlyArray" import * as U from "./util" describe("Foldable", () => { + it("toReadonlyArrayWith", () => { + const toReadonlyArrayWith = foldableWithIndex.toReadonlyArrayWith(O.FoldableWithIndex) + U.deepStrictEqual(toReadonlyArrayWith(O.none, U.double), []) + U.deepStrictEqual(toReadonlyArrayWith(O.some(2), U.double), [4]) + U.deepStrictEqual(toReadonlyArrayWith(O.some(2), (a, i) => U.double(a) + i), [4]) + }) + it("foldMapWithIndex", () => { - const foldMapWithIndex = foldableWithIndex.foldMapWithIndex(RA.FoldableWithIndexReadonlyArray) + const foldMapWithIndex = foldableWithIndex.foldMapWithIndex(RA.FoldableWithIndex) U.deepStrictEqual( - pipe([1, 2, 3], foldMapWithIndex(monoidTests.MonoidSum)((n, i) => (n * 2) + i)), + pipe([1, 2, 3], foldMapWithIndex(number.MonoidSum)((n, i) => (n * 2) + i)), 15 ) }) diff --git a/test/Functor.ts b/test/Functor.ts new file mode 100644 index 000000000..2c9bd1c29 --- /dev/null +++ b/test/Functor.ts @@ -0,0 +1,44 @@ +import * as _ from "@fp-ts/core/Functor" +import { pipe } from "@fp-ts/core/internal/Function" +import * as O from "./data/Option" +import * as U from "./util" + +describe("Functor", () => { + it("flap", () => { + const flap = _.flap(O.Functor) + U.deepStrictEqual(pipe(O.none, flap(1)), O.none) + U.deepStrictEqual(pipe(O.some(U.double), flap(1)), O.some(2)) + }) + + it("as", () => { + const as = _.as(O.Functor) + U.deepStrictEqual(pipe(O.none, as(1)), O.none) + U.deepStrictEqual(pipe(O.some(1), as(2)), O.some(2)) + }) + + it("as", () => { + const unit = _.unit(O.Functor) + U.deepStrictEqual(pipe(O.none, unit), O.none) + U.deepStrictEqual(pipe(O.some(1), unit), O.some(undefined)) + }) + + it("bindTo", () => { + const bindTo = _.bindTo(O.Functor) + U.deepStrictEqual(pipe(O.none, bindTo("a")), O.none) + U.deepStrictEqual(pipe(O.some(1), bindTo("a")), O.some({ a: 1 })) + }) + + it("let", () => { + const letOption = _.let(O.Functor) + U.deepStrictEqual( + pipe(O.some({ a: 1, b: 2 }), letOption("c", ({ a, b }) => a + b)), + O.some({ a: 1, b: 2, c: 3 }) + ) + }) + + it("tupled", () => { + const tupled = _.tupled(O.Functor) + U.deepStrictEqual(pipe(O.none, tupled), O.none) + U.deepStrictEqual(pipe(O.some(1), tupled), O.some([1] as const)) + }) +}) diff --git a/test/MonoidKind.ts b/test/MonoidKind.ts new file mode 100644 index 000000000..c82e222e6 --- /dev/null +++ b/test/MonoidKind.ts @@ -0,0 +1,14 @@ +import * as _ from "@fp-ts/core/MonoidKind" +import * as O from "./data/Option" +import * as U from "./util" + +describe("MonoidKind", () => { + it("fromSemigroupKind", () => { + const SemigroupKind = _.fromSemigroupKind(O.SemigroupKind, () => O.none) + U.deepStrictEqual(SemigroupKind.combineKindAll([]), O.none) + U.deepStrictEqual(SemigroupKind.combineKindAll([O.none]), O.none) + U.deepStrictEqual(SemigroupKind.combineKindAll([O.some(1)]), O.some(1)) + U.deepStrictEqual(SemigroupKind.combineKindAll([O.some(1), O.some(2)]), O.some(1)) + U.deepStrictEqual(SemigroupKind.combineKindAll([O.none, O.some(2)]), O.some(2)) + }) +}) diff --git a/test/Ordering.ts b/test/Ordering.ts index b1c6cba0e..3a2b6ddad 100644 --- a/test/Ordering.ts +++ b/test/Ordering.ts @@ -3,7 +3,7 @@ import * as _ from "@fp-ts/core/Ordering" import { deepStrictEqual } from "./util" describe("Ordering", () => { - it("Semigroup", () => { + it("combineMany", () => { deepStrictEqual(pipe(0, _.Monoid.combineMany([1, -1])), 1) deepStrictEqual(pipe(1, _.Monoid.combineMany([-1, -1])), 1) }) @@ -24,4 +24,19 @@ describe("Ordering", () => { deepStrictEqual(_.reverse(0), 0) deepStrictEqual(_.reverse(1), -1) }) + + it("Semigroup", () => { + deepStrictEqual(pipe(0, _.Semigroup.combine(0)), 0) + deepStrictEqual(pipe(0, _.Semigroup.combine(1)), 1) + deepStrictEqual(pipe(1, _.Semigroup.combine(-1)), 1) + deepStrictEqual(pipe(-1, _.Semigroup.combine(1)), -1) + + deepStrictEqual(pipe(0, _.Semigroup.combineMany([])), 0) + deepStrictEqual(pipe(1, _.Semigroup.combineMany([])), 1) + deepStrictEqual(pipe(-1, _.Semigroup.combineMany([])), -1) + deepStrictEqual(pipe(0, _.Semigroup.combineMany([0, 0, 0])), 0) + deepStrictEqual(pipe(0, _.Semigroup.combineMany([0, 0, 1])), 1) + deepStrictEqual(pipe(1, _.Semigroup.combineMany([0, 0, -1])), 1) + deepStrictEqual(pipe(-1, _.Semigroup.combineMany([0, 0, 1])), -1) + }) }) diff --git a/test/Semigroup.ts b/test/Semigroup.ts index f76e5fcba..b8996c676 100644 --- a/test/Semigroup.ts +++ b/test/Semigroup.ts @@ -1,25 +1,26 @@ import { pipe } from "@fp-ts/core/internal/Function" -import * as semigroup from "@fp-ts/core/Semigroup" +import * as _ from "@fp-ts/core/Semigroup" import * as sortable from "@fp-ts/core/Sortable" import * as number from "./data/number" import * as string from "./data/string" import * as U from "./util" describe("Semigroup", () => { - it("combine", () => { - const S = string.Semigroup - U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "ab") + it("reverse", () => { + const S = _.reverse(string.Semigroup) + // U.deepStrictEqual(pipe("a", S.combine("b")), "ba") + U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "ba") }) describe("min", () => { it("should return the minimum", () => { - const S = semigroup.min(number.Sortable) + const S = _.min(number.Sortable) U.deepStrictEqual(pipe(1, S.combineMany([3, 2])), 1) }) it("should return the last minimum", () => { type Item = { a: number } - const S = semigroup.min(pipe(number.Sortable, sortable.contramap((_: Item) => _.a))) + const S = _.min(pipe(number.Sortable, sortable.contramap((_: Item) => _.a))) const item: Item = { a: 1 } U.strictEqual(pipe({ a: 2 }, S.combineMany([{ a: 1 }, item])), item) }) @@ -27,20 +28,20 @@ describe("Semigroup", () => { describe("max", () => { it("should return the maximum", () => { - const S = semigroup.max(number.Sortable) + const S = _.max(number.Sortable) U.deepStrictEqual(pipe(1, S.combineMany([3, 2])), 3) }) it("should return the last minimum", () => { type Item = { a: number } - const S = semigroup.max(pipe(number.Sortable, sortable.contramap((_: Item) => _.a))) + const S = _.max(pipe(number.Sortable, sortable.contramap((_: Item) => _.a))) const item: Item = { a: 2 } U.strictEqual(pipe({ a: 1 }, S.combineMany([{ a: 2 }, item])), item) }) }) it("struct", () => { - const S = semigroup.struct({ + const S = _.struct({ name: string.Semigroup, age: number.SemigroupSum }) @@ -51,7 +52,7 @@ describe("Semigroup", () => { }) it("tuple", () => { - const S = semigroup.tuple( + const S = _.tuple( string.Semigroup, number.SemigroupSum ) @@ -59,12 +60,14 @@ describe("Semigroup", () => { }) it("first", () => { - const S = semigroup.first() + const S = _.first() + U.deepStrictEqual(pipe(1, S.combine(2)), 1) U.deepStrictEqual(pipe(1, S.combineMany([2, 3, 4, 5, 6])), 1) }) it("last", () => { - const S = semigroup.last() + const S = _.last() + U.deepStrictEqual(pipe(1, S.combine(2)), 2) U.deepStrictEqual(pipe(1, S.combineMany([])), 1) U.deepStrictEqual(pipe(1, S.combineMany([2, 3, 4, 5, 6])), 6) }) diff --git a/test/Sortable.ts b/test/Sortable.ts index dd15d9eec..2413c165b 100644 --- a/test/Sortable.ts +++ b/test/Sortable.ts @@ -48,21 +48,21 @@ describe("Sortable", () => { }) it("clamp", () => { - const clampNumber = _.clamp(number.Sortable) - U.deepStrictEqual(clampNumber(1, 10)(2), 2) - U.deepStrictEqual(clampNumber(1, 10)(10), 10) - U.deepStrictEqual(clampNumber(1, 10)(20), 10) - U.deepStrictEqual(clampNumber(1, 10)(1), 1) - U.deepStrictEqual(clampNumber(1, 10)(-10), 1) + const clamp = _.clamp(number.Sortable) + U.deepStrictEqual(clamp(1, 10)(2), 2) + U.deepStrictEqual(clamp(1, 10)(10), 10) + U.deepStrictEqual(clamp(1, 10)(20), 10) + U.deepStrictEqual(clamp(1, 10)(1), 1) + U.deepStrictEqual(clamp(1, 10)(-10), 1) }) it("between", () => { - const betweenNumber = _.between(number.Sortable) - U.deepStrictEqual(betweenNumber(1, 10)(2), true) - U.deepStrictEqual(betweenNumber(1, 10)(10), true) - U.deepStrictEqual(betweenNumber(1, 10)(20), false) - U.deepStrictEqual(betweenNumber(1, 10)(1), true) - U.deepStrictEqual(betweenNumber(1, 10)(-10), false) + const between = _.between(number.Sortable) + U.deepStrictEqual(between(1, 10)(2), true) + U.deepStrictEqual(between(1, 10)(10), true) + U.deepStrictEqual(between(1, 10)(20), false) + U.deepStrictEqual(between(1, 10)(1), true) + U.deepStrictEqual(between(1, 10)(-10), false) }) it("reverse", () => { diff --git a/test/data/Option.ts b/test/data/Option.ts index 4a174c9ce..2d942da77 100644 --- a/test/data/Option.ts +++ b/test/data/Option.ts @@ -1,5 +1,6 @@ import * as flatMap_ from "@fp-ts/core/FlatMap" import * as foldable from "@fp-ts/core/Foldable" +import type * as foldableWithIndex from "@fp-ts/core/FoldableWithIndex" import * as functor from "@fp-ts/core/Functor" import type { TypeLambda } from "@fp-ts/core/HKT" import type { Monoid } from "@fp-ts/core/Monoid" @@ -47,6 +48,17 @@ export const Foldable: foldable.Foldable = { reduceRight } +export const reduceWithIndex = (b: B, f: (b: B, a: A, i: number) => B) => + (self: Option): B => isNone(self) ? b : f(b, self.value, 0) + +export const reduceRightWithIndex = (b: B, f: (b: B, a: A, i: number) => B) => + (self: Option): B => isNone(self) ? b : f(b, self.value, 0) + +export const FoldableWithIndex: foldableWithIndex.FoldableWithIndex = { + reduceWithIndex, + reduceRightWithIndex +} + export const foldMap: ( Monoid: Monoid ) => (f: (a: A) => M) => (self: Option) => M = foldable.foldMap(Foldable) diff --git a/test/data/ReadonlyArray.ts b/test/data/ReadonlyArray.ts index ac540905c..b14d322fb 100644 --- a/test/data/ReadonlyArray.ts +++ b/test/data/ReadonlyArray.ts @@ -12,7 +12,7 @@ export interface ReadonlyArrayTypeLambda extends TypeLambda { readonly type: ReadonlyArray } -export const FoldableReadonlyArray: foldable.Foldable = { +export const Foldable: foldable.Foldable = { reduce: (b, f) => self => self.reduce((b, a) => f(b, a), b), reduceRight: (b, f) => self => self.reduceRight((b, a) => f(b, a), b) } @@ -25,7 +25,7 @@ export const reduceWithIndex = ( export const reduceRightWithIndex = (b: B, f: (b: B, a: A, i: number) => B) => (self: ReadonlyArray): B => self.reduceRight((b, a, i) => f(b, a, i), b) -export const FoldableWithIndexReadonlyArray: foldableWithIndex.FoldableWithIndex< +export const FoldableWithIndex: foldableWithIndex.FoldableWithIndex< ReadonlyArrayTypeLambda, number > = { diff --git a/test/data/number.ts b/test/data/number.ts index 8a1d56df8..ad11dea5f 100644 --- a/test/data/number.ts +++ b/test/data/number.ts @@ -4,13 +4,13 @@ import * as monoid from "@fp-ts/core/Monoid" import * as semigroup from "@fp-ts/core/Semigroup" import * as sortable from "@fp-ts/core/Sortable" -export const sum = (that: number) => (self: number): number => self + that +const sum = (that: number) => (self: number): number => self + that export const SemigroupSum: semigroup.Semigroup = semigroup.fromCombine(sum) export const MonoidSum: Monoid = monoid.fromSemigroup(SemigroupSum, 0) -export const multiply = (that: number) => (self: number): number => self * that +const multiply = (that: number) => (self: number): number => self * that export const SemigroupMultiply: semigroup.Semigroup = { combine: multiply, From 3cdd5eb1730e94ffb78d5c32e3b145fe4e97d981 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 07:49:38 +0200 Subject: [PATCH 02/12] Semigroup: fix reverse implementation --- .changeset/four-singers-sing.md | 5 +++++ src/Semigroup.ts | 2 +- test/Semigroup.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/four-singers-sing.md diff --git a/.changeset/four-singers-sing.md b/.changeset/four-singers-sing.md new file mode 100644 index 000000000..b301663a2 --- /dev/null +++ b/.changeset/four-singers-sing.md @@ -0,0 +1,5 @@ +--- +"@fp-ts/core": patch +--- + +Semigroup: fix reverse implementation diff --git a/src/Semigroup.ts b/src/Semigroup.ts index ea3c3cd5a..68cc2fce4 100644 --- a/src/Semigroup.ts +++ b/src/Semigroup.ts @@ -62,7 +62,7 @@ export const constant = (a: A): Semigroup => ({ * @since 1.0.0 */ export const reverse = (Semigroup: Semigroup): Semigroup => ({ - combine: (that) => (self) => Semigroup.combine(that)(self), + combine: (that) => (self) => Semigroup.combine(self)(that), combineMany: (collection) => (self) => { const reversed = Array.from(collection).reverse() diff --git a/test/Semigroup.ts b/test/Semigroup.ts index b8996c676..3d552792a 100644 --- a/test/Semigroup.ts +++ b/test/Semigroup.ts @@ -8,7 +8,7 @@ import * as U from "./util" describe("Semigroup", () => { it("reverse", () => { const S = _.reverse(string.Semigroup) - // U.deepStrictEqual(pipe("a", S.combine("b")), "ba") + U.deepStrictEqual(pipe("a", S.combine("b")), "ba") U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "ba") }) From f90041431ff34b2ce3d76a851c2b2854ec0dbf08 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 08:10:07 +0200 Subject: [PATCH 03/12] more tests --- test/Semigroup.ts | 15 ++++++++++ test/Semigroupal.ts | 19 +++++++----- test/Sortable.ts | 72 ++++++++++++++++++++++++++++++++++++++++----- test/data/Option.ts | 5 ---- test/data/Result.ts | 5 ---- 5 files changed, 90 insertions(+), 26 deletions(-) diff --git a/test/Semigroup.ts b/test/Semigroup.ts index 3d552792a..6bf606ca7 100644 --- a/test/Semigroup.ts +++ b/test/Semigroup.ts @@ -9,9 +9,24 @@ describe("Semigroup", () => { it("reverse", () => { const S = _.reverse(string.Semigroup) U.deepStrictEqual(pipe("a", S.combine("b")), "ba") + U.deepStrictEqual(pipe("a", S.combineMany([])), "a") U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "ba") }) + it("constant", () => { + const S = _.constant("c") + U.deepStrictEqual(pipe("a", S.combine("b")), "c") + U.deepStrictEqual(pipe("a", S.combineMany([])), "c") + U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "c") + }) + + it("intercalate", () => { + const S = pipe(string.Semigroup, _.intercalate("|")) + U.deepStrictEqual(pipe("a", S.combine("b")), "a|b") + U.deepStrictEqual(pipe("a", S.combineMany([])), "a") + U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "a|b") + }) + describe("min", () => { it("should return the minimum", () => { const S = _.min(number.Sortable) diff --git a/test/Semigroupal.ts b/test/Semigroupal.ts index 494d9ca84..823b58e12 100644 --- a/test/Semigroupal.ts +++ b/test/Semigroupal.ts @@ -4,6 +4,17 @@ import * as O from "./data/Option" import * as U from "./util" describe("Semigroupal", () => { + it("zipWithComposition", () => { + const sum = (a: number, b: number): number => a + b + const zipWith = _.zipWithComposition(O.Semigroupal, O.Semigroupal) + U.deepStrictEqual(pipe(O.none, zipWith(O.none, sum)), O.none) + U.deepStrictEqual(pipe(O.some(O.none), zipWith(O.none, sum)), O.none) + U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.none, sum)), O.none) + U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.some(O.none), sum)), O.some(O.none)) + U.deepStrictEqual(pipe(O.some(O.none), zipWith(O.some(O.some(2)), sum)), O.some(O.none)) + U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.some(O.some(2)), sum)), O.some(O.some(3))) + }) + it("zipManyComposition", () => { const zipMany = _.zipManyComposition(O.Semigroupal, O.Semigroupal) U.deepStrictEqual(pipe(O.none, zipMany([O.none])), O.none) @@ -32,14 +43,6 @@ describe("Semigroupal", () => { U.deepStrictEqual(pipe(O.some(1), zip(O.some("a"))), O.some([1, "a"] as const)) }) - it("zipWith", () => { - const zipWith = _.zipWith(O.Semigroupal) - const sum = (a: number, b: number) => a + b - U.deepStrictEqual(pipe(O.none, zipWith(O.none, sum)), O.none) - U.deepStrictEqual(pipe(O.some(1), zipWith(O.none, sum)), O.none) - U.deepStrictEqual(pipe(O.some(1), zipWith(O.some(2), sum)), O.some(3)) - }) - it("lift2", () => { const sum = _.lift2(O.Semigroupal)((a: number, b: number) => a + b) U.deepStrictEqual(sum(O.none, O.none), O.none) diff --git a/test/Sortable.ts b/test/Sortable.ts index 2413c165b..c1b734680 100644 --- a/test/Sortable.ts +++ b/test/Sortable.ts @@ -8,10 +8,54 @@ import * as U from "./util" describe("Sortable", () => { it("tuple", () => { - const O = _.tuple(string.Sortable, number.Sortable, boolean.Sortable) - U.deepStrictEqual(pipe(["a", 1, true], O.compare(["b", 2, true])), -1) - U.deepStrictEqual(pipe(["a", 1, true], O.compare(["a", 2, true])), -1) - U.deepStrictEqual(pipe(["a", 1, true], O.compare(["a", 1, false])), 1) + const S = _.tuple(string.Sortable, number.Sortable, boolean.Sortable) + U.deepStrictEqual(pipe(["a", 1, true], S.compare(["b", 2, true])), -1) + U.deepStrictEqual(pipe(["a", 1, true], S.compare(["a", 2, true])), -1) + U.deepStrictEqual(pipe(["a", 1, true], S.compare(["a", 1, false])), 1) + }) + + it("Contravariant", () => { + const S = pipe(number.Sortable, _.Contravariant.contramap((s: string) => s.length)) + U.deepStrictEqual(pipe("a", S.compare("b")), 0) + U.deepStrictEqual(pipe("a", S.compare("bb")), -1) + U.deepStrictEqual(pipe("aa", S.compare("b")), 1) + }) + + it("getSemigroup", () => { + type T = readonly [number, string] + const tuples: ReadonlyArray = [ + [2, "c"], + [1, "b"], + [2, "a"], + [1, "c"] + ] + const S = _.getSemigroup() + const sortByFst = pipe( + number.Sortable, + _.contramap((x: T) => x[0]) + ) + const sortBySnd = pipe( + string.Sortable, + _.contramap((x: T) => x[1]) + ) + U.deepStrictEqual(sort(pipe(sortByFst, S.combine(sortBySnd)))(tuples), [ + [1, "b"], + [1, "c"], + [2, "a"], + [2, "c"] + ]) + U.deepStrictEqual(sort(pipe(sortBySnd, S.combine(sortByFst)))(tuples), [ + [2, "a"], + [1, "b"], + [1, "c"], + [2, "c"] + ]) + U.deepStrictEqual(sort(pipe(sortBySnd, S.combineMany([])))(tuples), [ + [2, "a"], + [1, "b"], + [2, "c"], + [1, "c"] + ]) }) it("getMonoid", () => { @@ -31,15 +75,13 @@ describe("Sortable", () => { string.Sortable, _.contramap((x: T) => x[1]) ) - const O1 = pipe(M.empty, M.combineMany([sortByFst, sortBySnd])) - U.deepStrictEqual(sort(O1)(tuples), [ + U.deepStrictEqual(sort(pipe(M.empty, M.combineMany([sortByFst, sortBySnd])))(tuples), [ [1, "b"], [1, "c"], [2, "a"], [2, "c"] ]) - const O2 = pipe(sortBySnd, M.combineMany([sortByFst, M.empty])) - U.deepStrictEqual(sort(O2)(tuples), [ + U.deepStrictEqual(sort(pipe(sortBySnd, M.combineMany([sortByFst, M.empty])))(tuples), [ [2, "a"], [1, "b"], [1, "c"], @@ -72,6 +114,13 @@ describe("Sortable", () => { U.deepStrictEqual(pipe(2, Compare.compare(2)), 0) }) + it("lt", () => { + const lt = _.lt(number.Sortable) + U.deepStrictEqual(pipe(0, lt(1)), true) + U.deepStrictEqual(pipe(1, lt(1)), false) + U.deepStrictEqual(pipe(2, lt(1)), false) + }) + it("leq", () => { const leq = _.leq(number.Sortable) U.deepStrictEqual(pipe(0, leq(1)), true) @@ -79,6 +128,13 @@ describe("Sortable", () => { U.deepStrictEqual(pipe(2, leq(1)), false) }) + it("gt", () => { + const gt = _.gt(number.Sortable) + U.deepStrictEqual(pipe(0, gt(1)), false) + U.deepStrictEqual(pipe(1, gt(1)), false) + U.deepStrictEqual(pipe(2, gt(1)), true) + }) + it("geq", () => { const geq = _.geq(number.Sortable) U.deepStrictEqual(pipe(0, geq(1)), false) diff --git a/test/data/Option.ts b/test/data/Option.ts index 2d942da77..c48103bc5 100644 --- a/test/data/Option.ts +++ b/test/data/Option.ts @@ -135,11 +135,6 @@ export const zip: ( that: Option ) => (self: Option) => Option = semigroupal.zip(Semigroupal) -export const zipWith: ( - that: Option, - f: (a: A, b: B) => C -) => (self: Option) => Option = semigroupal.zipWith(Semigroupal) - export const ap: (fa: Option) => (fab: Option<(a: A) => B>) => Option = semigroupal .ap( Semigroupal diff --git a/test/data/Result.ts b/test/data/Result.ts index 0185c116b..ef140bf6b 100644 --- a/test/data/Result.ts +++ b/test/data/Result.ts @@ -61,8 +61,3 @@ export const Semigroupal: semigroupal.Semigroupal = { export const zip: ( that: Result ) => (self: Result) => Result = semigroupal.zip(Semigroupal) - -export const zipWith: ( - that: Result, - f: (a: A, b: B) => C -) => (self: Result) => Result = semigroupal.zipWith(Semigroupal) From a602af44eda7c0833b87894d420e76deee5fbbcd Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 08:10:53 +0200 Subject: [PATCH 04/12] Semigroupal: remove useless zipWith export --- .changeset/thin-moles-tell.md | 5 +++++ src/Semigroupal.ts | 11 ----------- 2 files changed, 5 insertions(+), 11 deletions(-) create mode 100644 .changeset/thin-moles-tell.md diff --git a/.changeset/thin-moles-tell.md b/.changeset/thin-moles-tell.md new file mode 100644 index 000000000..c744fe6ae --- /dev/null +++ b/.changeset/thin-moles-tell.md @@ -0,0 +1,5 @@ +--- +"@fp-ts/core": patch +--- + +Semigroupal: remove useless zipWith export diff --git a/src/Semigroupal.ts b/src/Semigroupal.ts index 72c717d95..3d6791135 100644 --- a/src/Semigroupal.ts +++ b/src/Semigroupal.ts @@ -93,17 +93,6 @@ export const zip = (Semigroupal: Semigroupal) => ): Kind => pipe(self, Semigroupal.zipWith(that, (a, b) => [a, b])) -/** - * Zips this effect with the specified effect using the - * specified combiner function. - * - * @since 1.0.0 - */ -export const zipWith = (Semigroupal: Semigroupal) => - (that: Kind, f: (a: A, b: B) => C) => - (self: Kind): Kind => - pipe(self, Semigroupal.zipWith(that, f)) - /** * Returns an effect that executes both this effect and the specified effect, * in parallel, this effect result returned. If either side fails, then the From 38329e3e2950f334c8a756ba90f99a48817004c3 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 08:16:26 +0200 Subject: [PATCH 05/12] more tests --- test/Semigroupal.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/Semigroupal.ts b/test/Semigroupal.ts index 823b58e12..3c814240b 100644 --- a/test/Semigroupal.ts +++ b/test/Semigroupal.ts @@ -1,6 +1,7 @@ import { pipe } from "@fp-ts/core/internal/Function" import * as _ from "@fp-ts/core/Semigroupal" import * as O from "./data/Option" +import * as string from "./data/string" import * as U from "./util" describe("Semigroupal", () => { @@ -43,6 +44,43 @@ describe("Semigroupal", () => { U.deepStrictEqual(pipe(O.some(1), zip(O.some("a"))), O.some([1, "a"] as const)) }) + it("zipLeftPar", () => { + const zipLeftPar = _.zipLeftPar(O.Semigroupal) + U.deepStrictEqual(pipe(O.none, zipLeftPar(O.none)), O.none) + U.deepStrictEqual(pipe(O.none, zipLeftPar(O.some(2))), O.none) + U.deepStrictEqual(pipe(O.some(1), zipLeftPar(O.none)), O.none) + U.deepStrictEqual(pipe(O.some(1), zipLeftPar(O.some(2))), O.some(1)) + }) + + it("zipRightPar", () => { + const zipRightPar = _.zipRightPar(O.Semigroupal) + U.deepStrictEqual(pipe(O.none, zipRightPar(O.none)), O.none) + U.deepStrictEqual(pipe(O.none, zipRightPar(O.some(2))), O.none) + U.deepStrictEqual(pipe(O.some(1), zipRightPar(O.none)), O.none) + U.deepStrictEqual(pipe(O.some(1), zipRightPar(O.some(2))), O.some(2)) + }) + + it("bindRight", () => { + const bindRight = _.bindRight(O.Semigroupal) + U.deepStrictEqual(pipe(O.some({ a: 1 }), bindRight("b", O.none)), O.none) + U.deepStrictEqual(pipe(O.some({ a: 1 }), bindRight("b", O.some(2))), O.some({ a: 1, b: 2 })) + }) + + it("zipFlatten", () => { + const zipFlatten = _.zipFlatten(O.Semigroupal) + U.deepStrictEqual(pipe(O.some([1, 2]), zipFlatten(O.none)), O.none) + U.deepStrictEqual(pipe(O.some([1, 2]), zipFlatten(O.some(3))), O.some([1, 2, 3] as const)) + }) + + it("liftSemigroup", () => { + const liftSemigroup = _.liftSemigroup(O.Semigroupal) + const S = liftSemigroup(string.Semigroup) + U.deepStrictEqual(pipe(O.none, S.combine(O.none)), O.none) + U.deepStrictEqual(pipe(O.none, S.combine(O.some("b"))), O.none) + U.deepStrictEqual(pipe(O.some("a"), S.combine(O.none)), O.none) + U.deepStrictEqual(pipe(O.some("a"), S.combine(O.some("b"))), O.some("ab")) + }) + it("lift2", () => { const sum = _.lift2(O.Semigroupal)((a: number, b: number) => a + b) U.deepStrictEqual(sum(O.none, O.none), O.none) From 8991751371a444cf4f361815feae903f7aee602c Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 08:20:24 +0200 Subject: [PATCH 06/12] Bounded: swap maximum, minimum arguments in fromSortable --- .changeset/thirty-nails-end.md | 5 +++++ src/Bounded.ts | 2 +- test/Bounded.ts | 2 +- test/data/number.ts | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/thirty-nails-end.md diff --git a/.changeset/thirty-nails-end.md b/.changeset/thirty-nails-end.md new file mode 100644 index 000000000..5f22c2444 --- /dev/null +++ b/.changeset/thirty-nails-end.md @@ -0,0 +1,5 @@ +--- +"@fp-ts/core": patch +--- + +Bounded: swap maximum, minimum arguments in fromSortable diff --git a/src/Bounded.ts b/src/Bounded.ts index 83bd75c4f..107bb3ec6 100644 --- a/src/Bounded.ts +++ b/src/Bounded.ts @@ -17,7 +17,7 @@ export interface Bounded extends Sortable { * @category constructors * @since 1.0.0 */ -export const fromSortable = (Sortable: Sortable, maximum: A, minimum: A): Bounded => ({ +export const fromSortable = (Sortable: Sortable, minimum: A, maximum: A): Bounded => ({ ...Sortable, maximum, minimum diff --git a/test/Bounded.ts b/test/Bounded.ts index 38b405c44..0c91a01dc 100644 --- a/test/Bounded.ts +++ b/test/Bounded.ts @@ -4,7 +4,7 @@ import * as U from "./util" describe("Bounded", () => { it("clamp", () => { - const clamp = _.clamp(_.fromSortable(number.Sortable, 10, 1)) + const clamp = _.clamp(_.fromSortable(number.Sortable, 1, 10)) U.deepStrictEqual(clamp(2), 2) U.deepStrictEqual(clamp(10), 10) U.deepStrictEqual(clamp(20), 10) diff --git a/test/data/number.ts b/test/data/number.ts index ad11dea5f..0de5b6881 100644 --- a/test/data/number.ts +++ b/test/data/number.ts @@ -34,4 +34,4 @@ export const Sortable: sortable.Sortable = sortable.fromCompare((that) = (self) => self < that ? -1 : self > that ? 1 : 0 ) -export const Bounded: bounded.Bounded = bounded.fromSortable(Sortable, Infinity, -Infinity) +export const Bounded: bounded.Bounded = bounded.fromSortable(Sortable, -Infinity, Infinity) From 86ebd5d269ef0c130a79e8b91cfae94e54ea6b74 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 08:32:25 +0200 Subject: [PATCH 07/12] Foldable / FlodableWithIndex: curry toReadonlyArrayWith and add toReadonlyArray --- .changeset/khaki-goats-flash.md | 5 +++++ src/Foldable.ts | 22 ++++++++++++++++------ src/FoldableWithIndex.ts | 22 ++++++++++++++++------ test/Foldable.ts | 10 ++++++++-- test/FoldableWithIndex.ts | 12 +++++++++--- test/Function.ts | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 .changeset/khaki-goats-flash.md create mode 100644 test/Function.ts diff --git a/.changeset/khaki-goats-flash.md b/.changeset/khaki-goats-flash.md new file mode 100644 index 000000000..297669bfc --- /dev/null +++ b/.changeset/khaki-goats-flash.md @@ -0,0 +1,5 @@ +--- +"@fp-ts/core": patch +--- + +Foldable / FlodableWithIndex: curry toReadonlyArrayWith and add toReadonlyArray diff --git a/src/Foldable.ts b/src/Foldable.ts index 22e67c3c5..0648bc6e2 100644 --- a/src/Foldable.ts +++ b/src/Foldable.ts @@ -3,6 +3,7 @@ */ import type { Kind, TypeClass, TypeLambda } from "@fp-ts/core/HKT" +import { identity } from "@fp-ts/core/internal/Function" import type { Monoid } from "@fp-ts/core/Monoid" /** @@ -21,17 +22,26 @@ export interface Foldable extends TypeClass { ) => (self: Kind) => B } +/** + * @since 1.0.0 + */ +export const toReadonlyArray = ( + Foldable: Foldable +): (self: Kind) => ReadonlyArray => + toReadonlyArrayWith(Foldable)(identity) + /** * @since 1.0.0 */ export const toReadonlyArrayWith = ( Foldable: Foldable ) => - (self: Kind, f: (a: A) => B): ReadonlyArray => - Foldable.reduce>([], (out, a) => { - out.push(f(a)) - return out - })(self) + (f: (a: A) => B) => + (self: Kind): ReadonlyArray => + Foldable.reduce>([], (out, a) => { + out.push(f(a)) + return out + })(self) /** * @since 1.0.0 @@ -40,4 +50,4 @@ export const foldMap = (Foldable: Foldable) => (Monoid: Monoid) => (f: (a: A) => M) => (self: Kind): M => - Monoid.combineAll(toReadonlyArrayWith(Foldable)(self, f)) + Monoid.combineAll(toReadonlyArrayWith(Foldable)(f)(self)) diff --git a/src/FoldableWithIndex.ts b/src/FoldableWithIndex.ts index 27659bcfc..bfcd99795 100644 --- a/src/FoldableWithIndex.ts +++ b/src/FoldableWithIndex.ts @@ -3,6 +3,7 @@ */ import type { Kind, TypeClass, TypeLambda } from "@fp-ts/core/HKT" +import { identity } from "@fp-ts/core/internal/Function" import type { Monoid } from "@fp-ts/core/Monoid" /** @@ -21,17 +22,26 @@ export interface FoldableWithIndex extends TypeClass ) => (self: Kind) => B } +/** + * @since 1.0.0 + */ +export const toReadonlyArray = ( + FoldableWithIndex: FoldableWithIndex +): (self: Kind) => ReadonlyArray => + toReadonlyArrayWith(FoldableWithIndex)(identity) + /** * @since 1.0.0 */ export const toReadonlyArrayWith = ( FoldableWithIndex: FoldableWithIndex ) => - (self: Kind, f: (a: A, i: I) => B): ReadonlyArray => - FoldableWithIndex.reduceWithIndex>([], (out, a, i) => { - out.push(f(a, i)) - return out - })(self) + (f: (a: A, i: I) => B) => + (self: Kind): ReadonlyArray => + FoldableWithIndex.reduceWithIndex>([], (out, a, i) => { + out.push(f(a, i)) + return out + })(self) /** * @since 1.0.0 @@ -42,4 +52,4 @@ export const foldMapWithIndex = ( (Monoid: Monoid) => (f: (a: A, i: I) => M) => (self: Kind): M => - Monoid.combineAll(toReadonlyArrayWith(FoldableWithIndex)(self, f)) + Monoid.combineAll(toReadonlyArrayWith(FoldableWithIndex)(f)(self)) diff --git a/test/Foldable.ts b/test/Foldable.ts index 39febe462..07e09f302 100644 --- a/test/Foldable.ts +++ b/test/Foldable.ts @@ -6,10 +6,16 @@ import * as RA from "./data/ReadonlyArray" import * as U from "./util" describe("Foldable", () => { + it("toReadonlyArray", () => { + const toReadonlyArray = foldable.toReadonlyArray(O.Foldable) + U.deepStrictEqual(toReadonlyArray(O.none), []) + U.deepStrictEqual(toReadonlyArray(O.some(2)), [2]) + }) + it("toReadonlyArrayWith", () => { const toReadonlyArrayWith = foldable.toReadonlyArrayWith(O.Foldable) - U.deepStrictEqual(toReadonlyArrayWith(O.none, U.double), []) - U.deepStrictEqual(toReadonlyArrayWith(O.some(2), U.double), [4]) + U.deepStrictEqual(pipe(O.none, toReadonlyArrayWith(U.double)), []) + U.deepStrictEqual(pipe(O.some(2), toReadonlyArrayWith(U.double)), [4]) }) it("foldMap", () => { diff --git a/test/FoldableWithIndex.ts b/test/FoldableWithIndex.ts index 9773bc67b..1230de1f6 100644 --- a/test/FoldableWithIndex.ts +++ b/test/FoldableWithIndex.ts @@ -6,11 +6,17 @@ import * as RA from "./data/ReadonlyArray" import * as U from "./util" describe("Foldable", () => { + it("toReadonlyArray", () => { + const toReadonlyArray = foldableWithIndex.toReadonlyArray(O.FoldableWithIndex) + U.deepStrictEqual(toReadonlyArray(O.none), []) + U.deepStrictEqual(toReadonlyArray(O.some(2)), [2]) + }) + it("toReadonlyArrayWith", () => { const toReadonlyArrayWith = foldableWithIndex.toReadonlyArrayWith(O.FoldableWithIndex) - U.deepStrictEqual(toReadonlyArrayWith(O.none, U.double), []) - U.deepStrictEqual(toReadonlyArrayWith(O.some(2), U.double), [4]) - U.deepStrictEqual(toReadonlyArrayWith(O.some(2), (a, i) => U.double(a) + i), [4]) + U.deepStrictEqual(pipe(O.none, toReadonlyArrayWith(U.double)), []) + U.deepStrictEqual(pipe(O.some(2), toReadonlyArrayWith(U.double)), [4]) + U.deepStrictEqual(pipe(O.some(2), toReadonlyArrayWith((a, i) => U.double(a) * i)), [0]) }) it("foldMapWithIndex", () => { diff --git a/test/Function.ts b/test/Function.ts new file mode 100644 index 000000000..9b7f9b906 --- /dev/null +++ b/test/Function.ts @@ -0,0 +1,33 @@ +import * as _ from "@fp-ts/core/internal/Function" +import * as U from "./util" + +describe("FlatMap", () => { + it("pipe", () => { + const f = (n: number): number => n + 1 + const g = U.double + U.deepStrictEqual(_.pipe(2), 2) + U.deepStrictEqual(_.pipe(2, f), 3) + U.deepStrictEqual(_.pipe(2, f, g), 6) + U.deepStrictEqual(_.pipe(2, f, g, f), 7) + U.deepStrictEqual(_.pipe(2, f, g, f, g), 14) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f), 15) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g), 30) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f), 31) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g), 62) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f), 63) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g), 126) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f), 127) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g), 254) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g, f), 255) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g, f, g), 510) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f), 511) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g), 1022) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f), 1023) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g), 2046) + U.deepStrictEqual(_.pipe(2, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f), 2047) + U.deepStrictEqual( + (_.pipe as any)(...[2, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g, f, g]), + 4094 + ) + }) +}) From afb92ce87180b680ca14d68286de8a241f8e9c69 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 09:06:38 +0200 Subject: [PATCH 08/12] add FunctorWithIndex module --- .changeset/short-radios-melt.md | 5 +++++ src/FunctorWithIndex.ts | 30 ++++++++++++++++++++++++++++++ src/index.ts | 6 ++++++ test/Functor.ts | 15 +++++++++++++++ test/FunctorWithIndex.ts | 20 ++++++++++++++++++++ test/data/ReadonlyArray.ts | 11 +++++++++++ 6 files changed, 87 insertions(+) create mode 100644 .changeset/short-radios-melt.md create mode 100644 src/FunctorWithIndex.ts create mode 100644 test/FunctorWithIndex.ts diff --git a/.changeset/short-radios-melt.md b/.changeset/short-radios-melt.md new file mode 100644 index 000000000..c2e90c548 --- /dev/null +++ b/.changeset/short-radios-melt.md @@ -0,0 +1,5 @@ +--- +"@fp-ts/core": patch +--- + +add FunctorWithIndex module diff --git a/src/FunctorWithIndex.ts b/src/FunctorWithIndex.ts new file mode 100644 index 000000000..073de42e5 --- /dev/null +++ b/src/FunctorWithIndex.ts @@ -0,0 +1,30 @@ +/** + * @since 1.0.0 + */ +import type { Kind, TypeClass, TypeLambda } from "@fp-ts/core/HKT" +import { pipe } from "@fp-ts/core/internal/Function" + +/** + * @category type class + * @since 1.0.0 + */ +export interface FunctorWithIndex extends TypeClass { + readonly mapWithIndex: ( + f: (a: A, i: I) => B + ) => (self: Kind) => Kind +} + +/** + * Returns a default `mapWithIndex` composition. + * + * @since 1.0.0 + */ +export const mapWithIndexComposition = ( + FunctorF: FunctorWithIndex, + FunctorG: FunctorWithIndex +): (( + f: (a: A, ij: readonly [I, J]) => B +) => ( + self: Kind> +) => Kind>) => + (f) => FunctorF.mapWithIndex((ga, i) => pipe(ga, FunctorG.mapWithIndex((a, j) => f(a, [i, j])))) diff --git a/src/index.ts b/src/index.ts index 2dfc69ab2..5a70379d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import * as flatMap from "@fp-ts/core/FlatMap" import * as foldable from "@fp-ts/core/Foldable" import * as foldableWithIndex from "@fp-ts/core/FoldableWithIndex" import * as functor from "@fp-ts/core/Functor" +import * as functorWithIndex from "@fp-ts/core/FunctorWithIndex" import * as hkt from "@fp-ts/core/HKT" import * as invariant from "@fp-ts/core/Invariant" import * as monad from "@fp-ts/core/Monad" @@ -96,6 +97,11 @@ export { * @since 1.0.0 */ functor, + /** + * @category type classes + * @since 1.0.0 + */ + functorWithIndex, /** * @since 1.0.0 */ diff --git a/test/Functor.ts b/test/Functor.ts index 2c9bd1c29..087d8087c 100644 --- a/test/Functor.ts +++ b/test/Functor.ts @@ -1,9 +1,24 @@ import * as _ from "@fp-ts/core/Functor" import { pipe } from "@fp-ts/core/internal/Function" import * as O from "./data/Option" +import * as RA from "./data/ReadonlyArray" import * as U from "./util" describe("Functor", () => { + it("mapWithIndexComposition", () => { + const map = _.mapComposition(RA.Functor, RA.Functor) + const f = (a: string) => a + "!" + U.deepStrictEqual(pipe([], map(f)), []) + U.deepStrictEqual(pipe([[]], map(f)), [[]]) + U.deepStrictEqual(pipe([["a"]], map(f)), [["a!"]]) + U.deepStrictEqual(pipe([["a"], ["b"]], map(f)), [["a!"], ["b!"]]) + U.deepStrictEqual(pipe([["a", "c"], ["b", "d", "e"]], map(f)), [["a!", "c!"], [ + "b!", + "d!", + "e!" + ]]) + }) + it("flap", () => { const flap = _.flap(O.Functor) U.deepStrictEqual(pipe(O.none, flap(1)), O.none) diff --git a/test/FunctorWithIndex.ts b/test/FunctorWithIndex.ts new file mode 100644 index 000000000..d7e3585a8 --- /dev/null +++ b/test/FunctorWithIndex.ts @@ -0,0 +1,20 @@ +import * as _ from "@fp-ts/core/FunctorWithIndex" +import { pipe } from "@fp-ts/core/internal/Function" +import * as RA from "./data/ReadonlyArray" +import * as U from "./util" + +describe("FunctorWithIndex", () => { + it("mapWithIndexComposition", () => { + const mapWithIndex = _.mapWithIndexComposition(RA.FunctorWithIndex, RA.FunctorWithIndex) + const f = (a: string, [i, j]: readonly [number, number]) => a + i + j + U.deepStrictEqual(pipe([], mapWithIndex(f)), []) + U.deepStrictEqual(pipe([[]], mapWithIndex(f)), [[]]) + U.deepStrictEqual(pipe([["a"]], mapWithIndex(f)), [["a00"]]) + U.deepStrictEqual(pipe([["a"], ["b"]], mapWithIndex(f)), [["a00"], ["b10"]]) + U.deepStrictEqual(pipe([["a", "c"], ["b", "d", "e"]], mapWithIndex(f)), [["a00", "c01"], [ + "b10", + "d11", + "e12" + ]]) + }) +}) diff --git a/test/data/ReadonlyArray.ts b/test/data/ReadonlyArray.ts index b14d322fb..cc05cbbbd 100644 --- a/test/data/ReadonlyArray.ts +++ b/test/data/ReadonlyArray.ts @@ -1,5 +1,7 @@ import type * as foldable from "@fp-ts/core/Foldable" import type * as foldableWithIndex from "@fp-ts/core/FoldableWithIndex" +import type * as functor from "@fp-ts/core/Functor" +import type * as functorWithIndex from "@fp-ts/core/FunctorWithIndex" import type { Kind, TypeLambda } from "@fp-ts/core/HKT" import type * as monoidal from "@fp-ts/core/Monoidal" import type { Sortable } from "@fp-ts/core/Sortable" @@ -12,6 +14,15 @@ export interface ReadonlyArrayTypeLambda extends TypeLambda { readonly type: ReadonlyArray } +export const Functor: functor.Functor = { + map: (f) => (self) => self.map(a => f(a)) +} + +export const FunctorWithIndex: functorWithIndex.FunctorWithIndex = + { + mapWithIndex: (f) => (self) => self.map((a, i) => f(a, i)) + } + export const Foldable: foldable.Foldable = { reduce: (b, f) => self => self.reduce((b, a) => f(b, a), b), reduceRight: (b, f) => self => self.reduceRight((b, a) => f(b, a), b) From e4ba0f40bb1360a6fe7c2fb2a37079f15ab75ca5 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 09:56:25 +0200 Subject: [PATCH 09/12] Foldable / FoldableWithIndex: add compositions --- .changeset/fuzzy-mugs-hope.md | 5 +++++ src/Foldable.ts | 30 +++++++++++++++++++++++++- src/FoldableWithIndex.ts | 40 ++++++++++++++++++++++++++++++++++- test/Foldable.ts | 19 +++++++++++++++++ test/FoldableWithIndex.ts | 30 +++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 .changeset/fuzzy-mugs-hope.md diff --git a/.changeset/fuzzy-mugs-hope.md b/.changeset/fuzzy-mugs-hope.md new file mode 100644 index 000000000..9a3c385c0 --- /dev/null +++ b/.changeset/fuzzy-mugs-hope.md @@ -0,0 +1,5 @@ +--- +"@fp-ts/core": patch +--- + +Foldable / FoldableWithIndex: add compositions diff --git a/src/Foldable.ts b/src/Foldable.ts index 0648bc6e2..98a63e619 100644 --- a/src/Foldable.ts +++ b/src/Foldable.ts @@ -3,7 +3,7 @@ */ import type { Kind, TypeClass, TypeLambda } from "@fp-ts/core/HKT" -import { identity } from "@fp-ts/core/internal/Function" +import { identity, pipe } from "@fp-ts/core/internal/Function" import type { Monoid } from "@fp-ts/core/Monoid" /** @@ -22,6 +22,34 @@ export interface Foldable extends TypeClass { ) => (self: Kind) => B } +/** + * Returns a default `reduce` composition. + * + * @since 1.0.0 + */ +export const reduceComposition = ( + F: Foldable, + G: Foldable +) => + (b: B, f: (b: B, a: A) => B) => + ( + self: Kind> + ): B => pipe(self, F.reduce(b, (b, ga) => pipe(ga, G.reduce(b, f)))) + +/** + * Returns a default `reduceRight` composition. + * + * @since 1.0.0 + */ +export const reduceRightComposition = ( + F: Foldable, + G: Foldable +) => + (b: B, f: (b: B, a: A) => B) => + ( + self: Kind> + ): B => pipe(self, F.reduceRight(b, (b, ga) => pipe(ga, G.reduceRight(b, f)))) + /** * @since 1.0.0 */ diff --git a/src/FoldableWithIndex.ts b/src/FoldableWithIndex.ts index bfcd99795..6fa38ea76 100644 --- a/src/FoldableWithIndex.ts +++ b/src/FoldableWithIndex.ts @@ -3,7 +3,7 @@ */ import type { Kind, TypeClass, TypeLambda } from "@fp-ts/core/HKT" -import { identity } from "@fp-ts/core/internal/Function" +import { identity, pipe } from "@fp-ts/core/internal/Function" import type { Monoid } from "@fp-ts/core/Monoid" /** @@ -22,6 +22,44 @@ export interface FoldableWithIndex extends TypeClass ) => (self: Kind) => B } +/** + * Returns a default `reduceWithIndex` composition. + * + * @since 1.0.0 + */ +export const reduceWithIndexComposition = ( + F: FoldableWithIndex, + G: FoldableWithIndex +) => + (b: B, f: (b: B, a: A, ij: readonly [I, J]) => B) => + ( + self: Kind> + ): B => + pipe( + self, + F.reduceWithIndex(b, (b, ga, i) => + pipe(ga, G.reduceWithIndex(b, (b, a, j) => f(b, a, [i, j])))) + ) + +/** + * Returns a default `reduceRightWithIndex` composition. + * + * @since 1.0.0 + */ +export const reduceRightWithIndexComposition = ( + F: FoldableWithIndex, + G: FoldableWithIndex +) => + (b: B, f: (b: B, a: A, ij: readonly [I, J]) => B) => + ( + self: Kind> + ): B => + pipe( + self, + F.reduceRightWithIndex(b, (b, ga, i) => + pipe(ga, G.reduceRightWithIndex(b, (b, a, j) => f(b, a, [i, j])))) + ) + /** * @since 1.0.0 */ diff --git a/test/Foldable.ts b/test/Foldable.ts index 07e09f302..dc64eddcb 100644 --- a/test/Foldable.ts +++ b/test/Foldable.ts @@ -6,6 +6,25 @@ import * as RA from "./data/ReadonlyArray" import * as U from "./util" describe("Foldable", () => { + it("reduceComposition", () => { + const reduce = foldable.reduceComposition(RA.Foldable, RA.Foldable) + const f = (b: string, a: string) => b + a + U.deepStrictEqual(pipe([], reduce("-", f)), "-") + U.deepStrictEqual(pipe([[]], reduce("-", f)), "-") + U.deepStrictEqual(pipe([["a", "c"], ["b", "d"]], reduce("-", f)), "-acbd") + }) + + it("reduceRightComposition", () => { + const reduceRight = foldable.reduceRightComposition(RA.Foldable, RA.Foldable) + const f = (b: string, a: string) => b + a + U.deepStrictEqual(pipe([], reduceRight("-", f)), "-") + U.deepStrictEqual(pipe([[]], reduceRight("-", f)), "-") + U.deepStrictEqual( + pipe([["a", "c"], ["b", "d"]], reduceRight("-", f)), + "-dbca" + ) + }) + it("toReadonlyArray", () => { const toReadonlyArray = foldable.toReadonlyArray(O.Foldable) U.deepStrictEqual(toReadonlyArray(O.none), []) diff --git a/test/FoldableWithIndex.ts b/test/FoldableWithIndex.ts index 1230de1f6..c075ff7a2 100644 --- a/test/FoldableWithIndex.ts +++ b/test/FoldableWithIndex.ts @@ -5,7 +5,35 @@ import * as O from "./data/Option" import * as RA from "./data/ReadonlyArray" import * as U from "./util" -describe("Foldable", () => { +describe("FoldableWithIndex", () => { + it("reduceWithIndexComposition", () => { + const reduceWithIndex = foldableWithIndex.reduceWithIndexComposition( + RA.FoldableWithIndex, + RA.FoldableWithIndex + ) + const f = (b: string, a: string, [i, j]: readonly [number, number]) => b + a + i + j + U.deepStrictEqual(pipe([], reduceWithIndex("-", f)), "-") + U.deepStrictEqual(pipe([[]], reduceWithIndex("-", f)), "-") + U.deepStrictEqual( + pipe([["a", "c"], ["b", "d"]], reduceWithIndex("-", f)), + "-a00c01b10d11" + ) + }) + + it("reduceRightWithIndexComposition", () => { + const reduceRightWithIndex = foldableWithIndex.reduceRightWithIndexComposition( + RA.FoldableWithIndex, + RA.FoldableWithIndex + ) + const f = (b: string, a: string, [i, j]: readonly [number, number]) => b + a + i + j + U.deepStrictEqual(pipe([], reduceRightWithIndex("-", f)), "-") + U.deepStrictEqual(pipe([[]], reduceRightWithIndex("-", f)), "-") + U.deepStrictEqual( + pipe([["a", "c"], ["b", "d"]], reduceRightWithIndex("-", f)), + "-d11b10c01a00" + ) + }) + it("toReadonlyArray", () => { const toReadonlyArray = foldableWithIndex.toReadonlyArray(O.FoldableWithIndex) U.deepStrictEqual(toReadonlyArray(O.none), []) From bc6c31218ded58062ab660b9fd4d2d7acf78e015 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 10:42:43 +0200 Subject: [PATCH 10/12] Semigroup: reverse should use combineMany --- src/Semigroup.ts | 4 +--- test/Semigroup.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Semigroup.ts b/src/Semigroup.ts index 68cc2fce4..9bb9560bb 100644 --- a/src/Semigroup.ts +++ b/src/Semigroup.ts @@ -68,9 +68,7 @@ export const reverse = (Semigroup: Semigroup): Semigroup => ({ const reversed = Array.from(collection).reverse() return reversed.length === 0 ? self : - Semigroup.combine(self)( - reversed.reduceRight((first, second) => Semigroup.combine(second)(first)) - ) + Semigroup.combine(self)(Semigroup.combineMany(reversed.slice(1))(reversed[0])) } }) diff --git a/test/Semigroup.ts b/test/Semigroup.ts index 6bf606ca7..6aa9ccf56 100644 --- a/test/Semigroup.ts +++ b/test/Semigroup.ts @@ -11,13 +11,14 @@ describe("Semigroup", () => { U.deepStrictEqual(pipe("a", S.combine("b")), "ba") U.deepStrictEqual(pipe("a", S.combineMany([])), "a") U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "ba") + U.deepStrictEqual(pipe("a", S.combineMany(["b", "c", "d"])), "dcba") }) it("constant", () => { - const S = _.constant("c") - U.deepStrictEqual(pipe("a", S.combine("b")), "c") - U.deepStrictEqual(pipe("a", S.combineMany([])), "c") - U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "c") + const S = _.constant("-") + U.deepStrictEqual(pipe("a", S.combine("b")), "-") + U.deepStrictEqual(pipe("a", S.combineMany([])), "-") + U.deepStrictEqual(pipe("a", S.combineMany(["b", "c", "d"])), "-") }) it("intercalate", () => { @@ -25,6 +26,7 @@ describe("Semigroup", () => { U.deepStrictEqual(pipe("a", S.combine("b")), "a|b") U.deepStrictEqual(pipe("a", S.combineMany([])), "a") U.deepStrictEqual(pipe("a", S.combineMany(["b"])), "a|b") + U.deepStrictEqual(pipe("a", S.combineMany(["b", "c", "d"])), "a|b|c|d") }) describe("min", () => { From fff6dc0b51962d793d200b155a23943c26eef797 Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 11:00:03 +0200 Subject: [PATCH 11/12] more tests --- test/Monoid.ts | 6 +++++- test/Ordering.ts | 5 ----- test/Semigroup.ts | 23 +++++++++++++++++++++++ test/Semigroupal.ts | 3 +++ test/Sortable.ts | 6 ++++++ 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/test/Monoid.ts b/test/Monoid.ts index f693551ce..2e5a7ee44 100644 --- a/test/Monoid.ts +++ b/test/Monoid.ts @@ -21,7 +21,11 @@ describe("Monoid", () => { it("reverse", () => { const M = monoid.reverse(string.Monoid) - U.deepStrictEqual(pipe("a", M.combineMany(["b"])), "ba") + U.deepStrictEqual(pipe("a", M.combine("b")), "ba") + U.deepStrictEqual(pipe("a", M.combine(M.empty)), "a") + U.deepStrictEqual(pipe(M.empty, M.combine("a")), "a") + U.deepStrictEqual(pipe("a", M.combineMany([])), "a") + U.deepStrictEqual(pipe("a", M.combineMany(["b", "c", "d"])), "dcba") U.deepStrictEqual(pipe("a", M.combineMany([M.empty])), "a") U.deepStrictEqual(pipe(M.empty, M.combineMany(["a"])), "a") }) diff --git a/test/Ordering.ts b/test/Ordering.ts index 3a2b6ddad..e8c467947 100644 --- a/test/Ordering.ts +++ b/test/Ordering.ts @@ -3,11 +3,6 @@ import * as _ from "@fp-ts/core/Ordering" import { deepStrictEqual } from "./util" describe("Ordering", () => { - it("combineMany", () => { - deepStrictEqual(pipe(0, _.Monoid.combineMany([1, -1])), 1) - deepStrictEqual(pipe(1, _.Monoid.combineMany([-1, -1])), 1) - }) - it("match", () => { const f = _.match( () => "lt", diff --git a/test/Semigroup.ts b/test/Semigroup.ts index 6aa9ccf56..26ec344eb 100644 --- a/test/Semigroup.ts +++ b/test/Semigroup.ts @@ -32,6 +32,7 @@ describe("Semigroup", () => { describe("min", () => { it("should return the minimum", () => { const S = _.min(number.Sortable) + U.deepStrictEqual(pipe(1, S.combineMany([])), 1) U.deepStrictEqual(pipe(1, S.combineMany([3, 2])), 1) }) @@ -40,12 +41,14 @@ describe("Semigroup", () => { const S = _.min(pipe(number.Sortable, sortable.contramap((_: Item) => _.a))) const item: Item = { a: 1 } U.strictEqual(pipe({ a: 2 }, S.combineMany([{ a: 1 }, item])), item) + U.strictEqual(pipe(item, S.combineMany([])), item) }) }) describe("max", () => { it("should return the maximum", () => { const S = _.max(number.Sortable) + U.deepStrictEqual(pipe(1, S.combineMany([])), 1) U.deepStrictEqual(pipe(1, S.combineMany([3, 2])), 3) }) @@ -54,6 +57,7 @@ describe("Semigroup", () => { const S = _.max(pipe(number.Sortable, sortable.contramap((_: Item) => _.a))) const item: Item = { a: 2 } U.strictEqual(pipe({ a: 1 }, S.combineMany([{ a: 2 }, item])), item) + U.strictEqual(pipe(item, S.combineMany([])), item) }) }) @@ -66,6 +70,21 @@ describe("Semigroup", () => { name: "ab", age: 30 }) + U.deepStrictEqual(pipe({ name: "a", age: 10 }, S.combineMany([])), { + name: "a", + age: 10 + }) + U.deepStrictEqual(pipe({ name: "a", age: 10 }, S.combineMany([{ name: "b", age: 20 }])), { + name: "ab", + age: 30 + }) + U.deepStrictEqual( + pipe({ name: "a", age: 10 }, S.combineMany([{ name: "b", age: 20 }, { name: "c", age: 30 }])), + { + name: "abc", + age: 60 + } + ) }) it("tuple", () => { @@ -74,11 +93,15 @@ describe("Semigroup", () => { number.SemigroupSum ) U.deepStrictEqual(pipe(["a", 10], S.combine(["b", 20])), ["ab", 30]) + U.deepStrictEqual(pipe(["a", 10], S.combineMany([])), ["a", 10]) + U.deepStrictEqual(pipe(["a", 10], S.combineMany([["b", 20]])), ["ab", 30]) + U.deepStrictEqual(pipe(["a", 10], S.combineMany([["b", 20], ["c", 30]])), ["abc", 60]) }) it("first", () => { const S = _.first() U.deepStrictEqual(pipe(1, S.combine(2)), 1) + U.deepStrictEqual(pipe(1, S.combineMany([])), 1) U.deepStrictEqual(pipe(1, S.combineMany([2, 3, 4, 5, 6])), 1) }) diff --git a/test/Semigroupal.ts b/test/Semigroupal.ts index 3c814240b..08a25fc05 100644 --- a/test/Semigroupal.ts +++ b/test/Semigroupal.ts @@ -18,6 +18,9 @@ describe("Semigroupal", () => { it("zipManyComposition", () => { const zipMany = _.zipManyComposition(O.Semigroupal, O.Semigroupal) + U.deepStrictEqual(pipe(O.none, zipMany([])), O.none) + U.deepStrictEqual(pipe(O.some(O.none), zipMany([])), O.some(O.none)) + U.deepStrictEqual(pipe(O.some(O.some(1)), zipMany([])), O.some(O.some([1] as const))) U.deepStrictEqual(pipe(O.none, zipMany([O.none])), O.none) U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.none])), O.none) U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.some(O.none)])), O.some(O.none)) diff --git a/test/Sortable.ts b/test/Sortable.ts index c1b734680..13d0eac31 100644 --- a/test/Sortable.ts +++ b/test/Sortable.ts @@ -56,6 +56,12 @@ describe("Sortable", () => { [2, "c"], [1, "c"] ]) + U.deepStrictEqual(sort(pipe(sortBySnd, S.combineMany([sortByFst])))(tuples), [ + [2, "a"], + [1, "b"], + [1, "c"], + [2, "c"] + ]) }) it("getMonoid", () => { From 126d40310eb35c6015dd1bf699a9abf37e3f69aa Mon Sep 17 00:00:00 2001 From: gcanti Date: Mon, 17 Oct 2022 11:47:03 +0200 Subject: [PATCH 12/12] add more tests to Semigroupal --- test/Semigroupal.ts | 86 ++++++++++++++++++++++++++++---------- test/data/ReadonlyArray.ts | 39 ++++++++++++++++- 2 files changed, 103 insertions(+), 22 deletions(-) diff --git a/test/Semigroupal.ts b/test/Semigroupal.ts index 08a25fc05..90e6d3c81 100644 --- a/test/Semigroupal.ts +++ b/test/Semigroupal.ts @@ -1,34 +1,78 @@ import { pipe } from "@fp-ts/core/internal/Function" import * as _ from "@fp-ts/core/Semigroupal" import * as O from "./data/Option" +import * as RA from "./data/ReadonlyArray" import * as string from "./data/string" import * as U from "./util" describe("Semigroupal", () => { - it("zipWithComposition", () => { + describe("zipWithComposition", () => { const sum = (a: number, b: number): number => a + b - const zipWith = _.zipWithComposition(O.Semigroupal, O.Semigroupal) - U.deepStrictEqual(pipe(O.none, zipWith(O.none, sum)), O.none) - U.deepStrictEqual(pipe(O.some(O.none), zipWith(O.none, sum)), O.none) - U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.none, sum)), O.none) - U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.some(O.none), sum)), O.some(O.none)) - U.deepStrictEqual(pipe(O.some(O.none), zipWith(O.some(O.some(2)), sum)), O.some(O.none)) - U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.some(O.some(2)), sum)), O.some(O.some(3))) + it("ReadonlyArray", () => { + const zipWith = _.zipWithComposition(RA.Semigroupal, O.Semigroupal) + U.deepStrictEqual(pipe([], zipWith([O.none], sum)), []) + U.deepStrictEqual(pipe([O.none], zipWith([], sum)), []) + U.deepStrictEqual(pipe([O.none], zipWith([O.none], sum)), [O.none]) + U.deepStrictEqual(pipe([O.some(1)], zipWith([O.some(2)], sum)), [O.some(3)]) + U.deepStrictEqual(pipe([O.some(1), O.none], zipWith([O.some(2)], sum)), [O.some(3), O.none]) + U.deepStrictEqual(pipe([O.some(1), O.none], zipWith([O.some(2), O.some(3)], sum)), [ + O.some(3), + O.some(4), + O.none, + O.none + ]) + + it("Option", () => { + const sum = (a: number, b: number): number => a + b + const zipWith = _.zipWithComposition(O.Semigroupal, O.Semigroupal) + U.deepStrictEqual(pipe(O.none, zipWith(O.none, sum)), O.none) + U.deepStrictEqual(pipe(O.some(O.none), zipWith(O.none, sum)), O.none) + U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.none, sum)), O.none) + U.deepStrictEqual(pipe(O.some(O.some(1)), zipWith(O.some(O.none), sum)), O.some(O.none)) + U.deepStrictEqual(pipe(O.some(O.none), zipWith(O.some(O.some(2)), sum)), O.some(O.none)) + U.deepStrictEqual( + pipe(O.some(O.some(1)), zipWith(O.some(O.some(2)), sum)), + O.some(O.some(3)) + ) + }) + }) }) - it("zipManyComposition", () => { - const zipMany = _.zipManyComposition(O.Semigroupal, O.Semigroupal) - U.deepStrictEqual(pipe(O.none, zipMany([])), O.none) - U.deepStrictEqual(pipe(O.some(O.none), zipMany([])), O.some(O.none)) - U.deepStrictEqual(pipe(O.some(O.some(1)), zipMany([])), O.some(O.some([1] as const))) - U.deepStrictEqual(pipe(O.none, zipMany([O.none])), O.none) - U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.none])), O.none) - U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.some(O.none)])), O.some(O.none)) - U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.some(O.some("a"))])), O.some(O.none)) - U.deepStrictEqual( - pipe(O.some(O.some(1)), zipMany([O.some(O.some(2))])), - O.some(O.some([1, 2] as const)) - ) + describe("zipManyComposition", () => { + it("ReadonlyArray", () => { + const zipMany = _.zipManyComposition(RA.Semigroupal, O.Semigroupal) + U.deepStrictEqual(pipe([O.some(1), O.none], zipMany([])), [O.some([1] as const), O.none]) + U.deepStrictEqual(pipe([O.some(1), O.none], zipMany([[O.some(2), O.none]])), [ + O.some([1, 2] as const), + O.none, + O.none, + O.none + ]) + U.deepStrictEqual( + pipe([O.some(1), O.some(2)], zipMany([[O.some(3), O.some(4)], [O.some(5)]])), + [ + O.some([1, 3, 5] as const), + O.some([1, 4, 5] as const), + O.some([2, 3, 5] as const), + O.some([2, 4, 5] as const) + ] + ) + }) + + it("Option", () => { + const zipMany = _.zipManyComposition(O.Semigroupal, O.Semigroupal) + U.deepStrictEqual(pipe(O.none, zipMany([])), O.none) + U.deepStrictEqual(pipe(O.some(O.none), zipMany([])), O.some(O.none)) + U.deepStrictEqual(pipe(O.some(O.some(1)), zipMany([])), O.some(O.some([1] as const))) + U.deepStrictEqual(pipe(O.none, zipMany([O.none])), O.none) + U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.none])), O.none) + U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.some(O.none)])), O.some(O.none)) + U.deepStrictEqual(pipe(O.some(O.none), zipMany([O.some(O.some("a"))])), O.some(O.none)) + U.deepStrictEqual( + pipe(O.some(O.some(1)), zipMany([O.some(O.some(2))])), + O.some(O.some([1, 2] as const)) + ) + }) }) it("ap", () => { diff --git a/test/data/ReadonlyArray.ts b/test/data/ReadonlyArray.ts index cc05cbbbd..414ad807f 100644 --- a/test/data/ReadonlyArray.ts +++ b/test/data/ReadonlyArray.ts @@ -3,7 +3,9 @@ import type * as foldableWithIndex from "@fp-ts/core/FoldableWithIndex" import type * as functor from "@fp-ts/core/Functor" import type * as functorWithIndex from "@fp-ts/core/FunctorWithIndex" import type { Kind, TypeLambda } from "@fp-ts/core/HKT" +import { pipe } from "@fp-ts/core/internal/Function" import type * as monoidal from "@fp-ts/core/Monoidal" +import type * as semigroupal from "@fp-ts/core/Semigroupal" import type { Sortable } from "@fp-ts/core/Sortable" import type * as traverse_ from "@fp-ts/core/Traversable" import type * as traverseWithIndex_ from "@fp-ts/core/TraversableWithIndex" @@ -14,8 +16,11 @@ export interface ReadonlyArrayTypeLambda extends TypeLambda { readonly type: ReadonlyArray } +const map = (f: (a: A) => B) => + (self: ReadonlyArray): ReadonlyArray => self.map(a => f(a)) + export const Functor: functor.Functor = { - map: (f) => (self) => self.map(a => f(a)) + map } export const FunctorWithIndex: functorWithIndex.FunctorWithIndex = @@ -90,3 +95,35 @@ export const TraverseWithIndex: traverseWithIndex_.TraversableWithIndex< > = { traverseWithIndex } + +export const zipWith = (that: ReadonlyArray, f: (a: A, b: B) => C) => + (self: ReadonlyArray): ReadonlyArray => { + const out: Array = [] + for (const a of self) { + for (const b of that) { + out.push(f(a, b)) + } + } + return out + } + +export const Semigroupal: semigroupal.Semigroupal = { + map, + zipWith, + zipMany: (collection: Iterable>) => + (self: ReadonlyArray): ReadonlyArray]> => { + const fa: ReadonlyArray]> = pipe( + self, + Functor.map(a => [a] as const) + ) + const fas = Array.from(collection) + if (fas.length === 0) { + return fa + } + let out = fa + for (const c of fas) { + out = pipe(out, zipWith(c, (a, b) => [...a, b])) + } + return out + } +}