diff --git a/.changeset/heavy-lobsters-try.md b/.changeset/heavy-lobsters-try.md new file mode 100644 index 0000000..d2c5bbb --- /dev/null +++ b/.changeset/heavy-lobsters-try.md @@ -0,0 +1,5 @@ +--- +"@fp-ts/optic": patch +--- + +add findFirst diff --git a/dtslint/ts4.7/index.ts b/dtslint/ts4.7/index.ts index bbbffb2..42cf183 100644 --- a/dtslint/ts4.7/index.ts +++ b/dtslint/ts4.7/index.ts @@ -49,3 +49,13 @@ declare const predicate: (u: string | number | boolean) => boolean // $ExpectType Prism Optic.id().compose(Optic.filter(predicate)) + +// +// findFirst +// + +// $ExpectType Optional +Optic.id>().compose(Optic.findFirst(isString)) + +// $ExpectType Optional +Optic.id>().compose(Optic.findFirst(predicate)) diff --git a/src/index.ts b/src/index.ts index 5c09a40..c3f36c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -508,6 +508,36 @@ export const head = (): Optional, A> => cons().compose(at export const tail = (): Optional, ReadonlyArray> => cons().compose(at("1")) +/** + * An optic that accesses the first case specified by a predicate. + * + * @since 1.0.0 + */ +export const findFirst: { + (refinement: Refinement): Optional, B> + (predicate: Predicate): Optional, B> +} = (predicate: Predicate): Optional, A> => + optional( + (as) => + pipe( + as, + RA.findFirst(predicate), + E.fromOption(() => new Error(`[${as}] did not satisfy the specified predicate`)) + ), + (a) => + (as) => + pipe( + as, + RA.findFirstIndex(predicate), + E.fromOption(() => new Error(`[${as}] did not satisfy the specified predicate`)), + E.map((index) => { + const out = as.slice() + out[index] = a + return out + }) + ) + ) + /** * @since 1.0.0 */ diff --git a/test/index.ts b/test/index.ts index 6d96978..8fd2efb 100644 --- a/test/index.ts +++ b/test/index.ts @@ -181,4 +181,17 @@ describe("index", () => { expect(pipe([true, "a"], Optic.encode(_A))).toEqual([true, "a"]) }) + + it("findFirst", () => { + const isString = (u: unknown): u is string => typeof u === "string" + const _firstString = Optic.id>() + .compose(Optic.findFirst(isString)) + expect(pipe([1, 2, "a", 3, "b"], Optic.getOption(_firstString))).toEqual(O.some("a")) + expect(pipe([1, 2, 3], Optic.getOption(_firstString))).toEqual(O.none) + + expect(pipe([1, 2, "a", 3, "b"], Optic.replaceOption(_firstString)("c"))).toEqual( + O.some([1, 2, "c", 3, "b"]) + ) + expect(pipe([1, 2, 3], Optic.replaceOption(_firstString)("c"))).toEqual(O.none) + }) })