Skip to content

Commit

Permalink
add findFirst
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Nov 26, 2022
1 parent 2209531 commit 09c6ddf
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-lobsters-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fp-ts/optic": patch
---

add findFirst
10 changes: 10 additions & 0 deletions dtslint/ts4.7/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,13 @@ declare const predicate: (u: string | number | boolean) => boolean

// $ExpectType Prism<string | number, string | number>
Optic.id<string | number>().compose(Optic.filter(predicate))

//
// findFirst
//

// $ExpectType Optional<readonly (string | number)[], string>
Optic.id<ReadonlyArray<string | number>>().compose(Optic.findFirst(isString))

// $ExpectType Optional<readonly (string | number)[], string | number>
Optic.id<ReadonlyArray<string | number>>().compose(Optic.findFirst(predicate))
30 changes: 30 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,36 @@ export const head = <A>(): Optional<ReadonlyArray<A>, A> => cons<A>().compose(at
export const tail = <A>(): Optional<ReadonlyArray<A>, ReadonlyArray<A>> =>
cons<A>().compose(at("1"))

/**
* An optic that accesses the first case specified by a predicate.
*
* @since 1.0.0
*/
export const findFirst: {
<C extends A, B extends A, A = C>(refinement: Refinement<A, B>): Optional<ReadonlyArray<C>, B>
<B extends A, A = B>(predicate: Predicate<A>): Optional<ReadonlyArray<B>, B>
} = <A>(predicate: Predicate<A>): Optional<ReadonlyArray<A>, 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
*/
Expand Down
13 changes: 13 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReadonlyArray<string | number>>()
.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)
})
})

0 comments on commit 09c6ddf

Please sign in to comment.