From 072421149c36010748ff6b6ee19c15c6cffefe09 Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Wed, 3 Jan 2024 14:33:23 +0100 Subject: [PATCH] =?UTF-8?q?Add=20Option-returning=20overloads=20for=20find?= =?UTF-8?q?First=20and=20findLast=20in=20Readonly=E2=80=A6=20(#1854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/green-squids-travel.md | 5 ++ packages/effect/dtslint/ReadonlyArray.ts | 86 ++++++++++++++++++++++ packages/effect/src/ReadonlyArray.ts | 72 ++++++++++++------ packages/effect/test/ReadonlyArray.test.ts | 84 +++++++++++++++++---- 4 files changed, 209 insertions(+), 38 deletions(-) create mode 100644 .changeset/green-squids-travel.md diff --git a/.changeset/green-squids-travel.md b/.changeset/green-squids-travel.md new file mode 100644 index 0000000000..62f818a0d9 --- /dev/null +++ b/.changeset/green-squids-travel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add Option-returning overloads for findFirst and findLast in ReadonlyArray diff --git a/packages/effect/dtslint/ReadonlyArray.ts b/packages/effect/dtslint/ReadonlyArray.ts index 7586d5a92e..9bdc84623a 100644 --- a/packages/effect/dtslint/ReadonlyArray.ts +++ b/packages/effect/dtslint/ReadonlyArray.ts @@ -292,6 +292,23 @@ ReadonlyArray.findFirst(numbersOrStrings, ( _item // $ExpectType string | number ) => true) +ReadonlyArray.findFirst(numbersOrStrings, ( + _item, // $ExpectType string | number + _i // $ExpectType number +) => true) + +// $ExpectType Option +ReadonlyArray.findFirst(numbersOrStrings, ( + _item, // $ExpectType string | number + _i // $ExpectType number +): _item is number => true) + +// $ExpectType Option +ReadonlyArray.findFirst(numbersOrStrings, ( + _item, // $ExpectType string | number + _i // $ExpectType number +) => Option.some(true)) + pipe( numbersOrStrings, ReadonlyArray.findFirst(( @@ -299,6 +316,32 @@ pipe( ) => true) ) +pipe( + numbersOrStrings, + ReadonlyArray.findFirst(( + _item, // $ExpectType string | number + _i // $ExpectType number + ) => true) +) + +// $ExpectType Option +pipe( + numbersOrStrings, + ReadonlyArray.findFirst(( + _item, // $ExpectType string | number + _i // $ExpectType number + ): _item is number => true) +) + +// $ExpectType Option +pipe( + numbersOrStrings, + ReadonlyArray.findFirst(( + _item, // $ExpectType string | number + _i // $ExpectType number + ) => Option.some(true)) +) + // $ExpectType Option ReadonlyArray.findFirst(numbersOrStrings, predicateNumbersOrStrings) @@ -319,6 +362,23 @@ ReadonlyArray.findLast(numbersOrStrings, ( _item // $ExpectType string | number ) => true) +ReadonlyArray.findLast(numbersOrStrings, ( + _item, // $ExpectType string | number + _i // $ExpectType number +) => true) + +// $ExpectType Option +ReadonlyArray.findLast(numbersOrStrings, ( + _item, // $ExpectType string | number + _i // $ExpectType number +): _item is number => true) + +// $ExpectType Option +ReadonlyArray.findLast(numbersOrStrings, ( + _item, // $ExpectType string | number + _i // $ExpectType number +) => Option.some(true)) + pipe( numbersOrStrings, ReadonlyArray.findLast(( @@ -326,6 +386,32 @@ pipe( ) => true) ) +pipe( + numbersOrStrings, + ReadonlyArray.findLast(( + _item, // $ExpectType string | number + _i // $ExpectType number + ) => true) +) + +// $ExpectType Option +pipe( + numbersOrStrings, + ReadonlyArray.findLast(( + _item, // $ExpectType string | number + _i // $ExpectType number + ): _item is number => true) +) + +// $ExpectType Option +pipe( + numbersOrStrings, + ReadonlyArray.findLast(( + _item, // $ExpectType string | number + _i // $ExpectType number + ) => Option.some(true)) +) + // $ExpectType Option ReadonlyArray.findLast(numbersOrStrings, predicateNumbersOrStrings) diff --git a/packages/effect/src/ReadonlyArray.ts b/packages/effect/src/ReadonlyArray.ts index c6ee305ec1..39558d4d03 100644 --- a/packages/effect/src/ReadonlyArray.ts +++ b/packages/effect/src/ReadonlyArray.ts @@ -15,7 +15,7 @@ import * as readonlyArray from "./internal/readonlyArray.js" import type { Option } from "./Option.js" import * as O from "./Option.js" import * as Order from "./Order.js" -import { not } from "./Predicate.js" +import { isBoolean, not } from "./Predicate.js" import type { Predicate, Refinement } from "./Predicate.js" import * as RR from "./ReadonlyRecord.js" import * as Tuple from "./Tuple.js" @@ -700,19 +700,32 @@ export const findLastIndex: { * @since 2.0.0 */ export const findFirst: { - (refinement: Refinement): (self: Iterable) => Option - (predicate: Predicate): (self: Iterable) => Option - (self: Iterable, refinement: Refinement): Option - (self: Iterable, predicate: Predicate): Option -} = dual(2, (self: Iterable, predicate: Predicate): Option => { - const input = fromIterable(self) - for (let i = 0; i < input.length; i++) { - if (predicate(input[i])) { - return O.some(input[i]) + (f: (a: A, i: number) => Option): (self: Iterable) => Option + (refinement: (a: A, i: number) => a is B): (self: Iterable) => Option + (predicate: (a: A, i: number) => boolean): (self: Iterable) => Option + (self: Iterable, f: (a: A, i: number) => Option): Option + (self: Iterable, refinement: (a: A, i: number) => a is B): Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option +} = dual( + 2, + (self: Iterable, f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option)): Option => { + let i = 0 + for (const a of self) { + const o = f(a, i) + if (isBoolean(o)) { + if (o) { + return O.some(a) + } + } else { + if (O.isSome(o)) { + return o + } + } + i++ } + return O.none() } - return O.none() -}) +) /** * Find the last element for which a predicate holds. @@ -721,19 +734,32 @@ export const findFirst: { * @since 2.0.0 */ export const findLast: { - (refinement: Refinement): (self: Iterable) => Option - (predicate: Predicate): (self: Iterable) => Option - (self: Iterable, refinement: Refinement): Option - (self: Iterable, predicate: Predicate): Option -} = dual(2, (self: Iterable, predicate: Predicate): Option => { - const input = fromIterable(self) - for (let i = input.length - 1; i >= 0; i--) { - if (predicate(input[i])) { - return O.some(input[i]) + (f: (a: A, i: number) => Option): (self: Iterable) => Option + (refinement: (a: A, i: number) => a is B): (self: Iterable) => Option + (predicate: (a: A, i: number) => boolean): (self: Iterable) => Option + (self: Iterable, f: (a: A, i: number) => Option): Option + (self: Iterable, refinement: (a: A, i: number) => a is B): Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option +} = dual( + 2, + (self: Iterable, f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option)): Option => { + const input = fromIterable(self) + for (let i = input.length - 1; i >= 0; i--) { + const a = input[i] + const o = f(a, i) + if (isBoolean(o)) { + if (o) { + return O.some(a) + } + } else { + if (O.isSome(o)) { + return o + } + } } + return O.none() } - return O.none() -}) +) /** * Insert an element at the specified index, creating a new `NonEmptyArray`, diff --git a/packages/effect/test/ReadonlyArray.test.ts b/packages/effect/test/ReadonlyArray.test.ts index 365276b3ce..581b0b2344 100644 --- a/packages/effect/test/ReadonlyArray.test.ts +++ b/packages/effect/test/ReadonlyArray.test.ts @@ -287,24 +287,78 @@ describe("ReadonlyArray", () => { deepStrictEqual(pipe(new Set([1, 2, 3, 4]), RA.findLastIndex((n) => n % 2 === 0)), O.some(3)) }) - it("findFirst", () => { - deepStrictEqual(pipe([], RA.findFirst((n) => n % 2 === 0)), O.none()) - deepStrictEqual(pipe([1, 2, 3], RA.findFirst((n) => n % 2 === 0)), O.some(2)) - deepStrictEqual(pipe([1, 2, 3, 4], RA.findFirst((n) => n % 2 === 0)), O.some(2)) - - deepStrictEqual(pipe(new Set(), RA.findFirst((n) => n % 2 === 0)), O.none()) - deepStrictEqual(pipe(new Set([1, 2, 3]), RA.findFirst((n) => n % 2 === 0)), O.some(2)) - deepStrictEqual(pipe(new Set([1, 2, 3, 4]), RA.findFirst((n) => n % 2 === 0)), O.some(2)) + describe("findFirst", () => { + it("boolean-returning overloads", () => { + deepStrictEqual(pipe([], RA.findFirst((n) => n % 2 === 0)), O.none()) + deepStrictEqual(pipe([1, 2, 3], RA.findFirst((n) => n % 2 === 0)), O.some(2)) + deepStrictEqual(pipe([1, 2, 3, 4], RA.findFirst((n) => n % 2 === 0)), O.some(2)) + + deepStrictEqual(pipe(new Set(), RA.findFirst((n) => n % 2 === 0)), O.none()) + deepStrictEqual(pipe(new Set([1, 2, 3]), RA.findFirst((n) => n % 2 === 0)), O.some(2)) + deepStrictEqual(pipe(new Set([1, 2, 3, 4]), RA.findFirst((n) => n % 2 === 0)), O.some(2)) + }) + + it("Option-returning overloads", () => { + deepStrictEqual(pipe([], RA.findFirst((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), O.none()) + deepStrictEqual( + pipe([1, 2, 3], RA.findFirst((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([2, 1]) + ) + deepStrictEqual( + pipe([1, 2, 3, 4], RA.findFirst((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([2, 1]) + ) + + deepStrictEqual( + pipe(new Set(), RA.findFirst((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.none() + ) + deepStrictEqual( + pipe(new Set([1, 2, 3]), RA.findFirst((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([2, 1]) + ) + deepStrictEqual( + pipe(new Set([1, 2, 3, 4]), RA.findFirst((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([2, 1]) + ) + }) }) - it("findLast", () => { - deepStrictEqual(pipe([], RA.findLast((n) => n % 2 === 0)), O.none()) - deepStrictEqual(pipe([1, 2, 3], RA.findLast((n) => n % 2 === 0)), O.some(2)) - deepStrictEqual(pipe([1, 2, 3, 4], RA.findLast((n) => n % 2 === 0)), O.some(4)) + describe("findLast", () => { + it("boolean-returning overloads", () => { + deepStrictEqual(pipe([], RA.findLast((n) => n % 2 === 0)), O.none()) + deepStrictEqual(pipe([1, 2, 3], RA.findLast((n) => n % 2 === 0)), O.some(2)) + deepStrictEqual(pipe([1, 2, 3, 4], RA.findLast((n) => n % 2 === 0)), O.some(4)) + + deepStrictEqual(pipe(new Set(), RA.findLast((n) => n % 2 === 0)), O.none()) + deepStrictEqual(pipe(new Set([1, 2, 3]), RA.findLast((n) => n % 2 === 0)), O.some(2)) + deepStrictEqual(pipe(new Set([1, 2, 3, 4]), RA.findLast((n) => n % 2 === 0)), O.some(4)) + }) + + it("Option-returning overloads", () => { + deepStrictEqual(pipe([], RA.findLast((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), O.none()) + deepStrictEqual( + pipe([1, 2, 3], RA.findLast((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([2, 1]) + ) + deepStrictEqual( + pipe([1, 2, 3, 4], RA.findLast((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([4, 3]) + ) - deepStrictEqual(pipe(new Set(), RA.findLast((n) => n % 2 === 0)), O.none()) - deepStrictEqual(pipe(new Set([1, 2, 3]), RA.findLast((n) => n % 2 === 0)), O.some(2)) - deepStrictEqual(pipe(new Set([1, 2, 3, 4]), RA.findLast((n) => n % 2 === 0)), O.some(4)) + deepStrictEqual( + pipe(new Set(), RA.findLast((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.none() + ) + deepStrictEqual( + pipe(new Set([1, 2, 3]), RA.findLast((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([2, 1]) + ) + deepStrictEqual( + pipe(new Set([1, 2, 3, 4]), RA.findLast((n, i) => n % 2 === 0 ? O.some([n, i]) : O.none())), + O.some([4, 3]) + ) + }) }) it("insertAt", () => {