Skip to content

Commit

Permalink
Optic: add nonNullable
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Nov 29, 2022
1 parent f45bdfa commit d402495
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/slimy-trees-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fp-ts/optic": patch
---

Optic: add nonNullable
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ flowchart TD
Let's say we have an employee and we need to upper case the first character of his company street name.

```ts
import * as O from "@fp-ts/data/Option";

interface Street {
num: number;
name: O.Option<string>;
name: string | null;
}
interface Address {
city: string;
Expand All @@ -69,7 +67,7 @@ const from: Employee = {
city: "london",
street: {
num: 23,
name: O.some("high street"),
name: "high street",
},
},
},
Expand All @@ -83,7 +81,7 @@ const to: Employee = {
city: "london",
street: {
num: 23,
name: O.some("High street"),
name: "High street",
},
},
},
Expand All @@ -94,16 +92,15 @@ Let's see what could we do with `@fp-ts/optic`

```ts
import * as Optic from "@fp-ts/optic";
import * as OptionOptic from "@fp-ts/optic/data/Option";
import * as StringOptic from "@fp-ts/optic/data/string";

const _name: Optic.Optional<Employee, string> = Optic.id<Employee>()
.at("company") // Lens<Employee, Company>
.at("address") // Lens<Employee, Company>
.at("street") // Lens<<Employee, Company>
.at("name") // Lens<Street, O.Option<string>>
.compose(OptionOptic.some()) // Prism<O.Option<string>, string>
.compose(StringOptic.index(0)); // Optional<string, string>
.at("company")
.at("address")
.at("street")
.at("name")
.nonNullable()
.compose(StringOptic.index(0));

const capitalize = (s: string): string => s.toUpperCase();

Expand Down
4 changes: 2 additions & 2 deletions src/data/Option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const none = <A>(): Prism<Option<A>, void> =>
() => E.right<void>(undefined),
(a) => E.left(Error(`some(${a}) did not satisfy isNone`))
),
(_): Option<A> => O.none
(): Option<A> => O.none
)

/**
Expand All @@ -35,5 +35,5 @@ export const some: {
() => E.left([Error("none did not satisfy isSome"), O.none]),
(a) => E.right(a)
),
(b) => O.some(b)
O.some
)
30 changes: 17 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ export interface Optic<
key: Key
): Optional<S, A[Key]>

nonNullable<S, A>(this: Prism<S, A>): Prism<S, NonNullable<A>>
nonNullable<S, A>(this: Optional<S, A>): Optional<S, NonNullable<A>>

get<S, T, A, B>(this: PolyLens<S, T, A, B>, s: S): A

getOption<S, A>(this: Getter<S, A>, s: S): Option<A>
Expand Down Expand Up @@ -140,6 +143,10 @@ class Builder<
return this.compose(at<any, any>(key))
}

nonNullable(): any {
return this.compose(nonNullable())
}

get<S, T, A, B>(this: PolyLens<S, T, A, B>, s: S): A {
return pipe(this.getOptic(s), E.getOrThrow(identity))
}
Expand Down Expand Up @@ -433,17 +440,6 @@ export const cons: {
([head, tail]): ReadonlyArray<B> => [head, ...tail]
)

/**
* An optic that accesses the `NonNullable` case of a nullable type.
*
* @since 1.0.0
*/
export const nonNullable = <S>(): Prism<S, NonNullable<S>> =>
prism(
(s) => s == null ? E.left(new Error(`${s} did not satisfy isNonNullable`)) : E.right(s as any),
identity
)

/**
* An optic that accesses the case specified by a predicate.
*
Expand All @@ -454,11 +450,19 @@ export const filter: {
<S extends A, A = S>(predicate: Predicate<A>): Prism<S, S>
} = <S>(predicate: Predicate<S>): Prism<S, S> =>
prism(
(s) =>
predicate(s) ? E.right(s) : E.left(new Error(`${s} did not satisfy the specified predicate`)),
(s) => predicate(s) ? E.right(s) : E.left(new Error(`${s} did not satisfy ${predicate.name}`)),
identity
)

const isNonNullable = <S>(s: S): s is NonNullable<S> => s != null

/**
* An optic that accesses the `NonNullable` case of a nullable type.
*
* @since 1.0.0
*/
export const nonNullable = <S>(): Prism<S, NonNullable<S>> => filter(isNonNullable)

/**
* @since 1.0.0
*/
Expand Down
20 changes: 9 additions & 11 deletions test/examples.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import * as O from "@fp-ts/data/Option"
import * as Optic from "@fp-ts/optic"
import * as OptionOptic from "@fp-ts/optic/data/Option"
import * as StringOptic from "@fp-ts/optic/data/String"

describe("examples", () => {
it("README", () => {
interface Street {
num: number
name: O.Option<string>
name: string | null
}
interface Address {
city: string
Expand All @@ -30,7 +28,7 @@ describe("examples", () => {
city: "london",
street: {
num: 23,
name: O.some("high street")
name: "high street"
}
}
}
Expand All @@ -44,19 +42,19 @@ describe("examples", () => {
city: "london",
street: {
num: 23,
name: O.some("High street")
name: "High street"
}
}
}
}

const _name: Optic.Optional<Employee, string> = Optic.id<Employee>()
.at("company") // Lens<Employee, Company>
.at("address") // Lens<Employee, Company>
.at("street") // Lens<<Employee, Company>
.at("name") // Lens<Street, O.Option<string>>
.compose(OptionOptic.some()) // Prism<O.Option<string>, string>
.compose(StringOptic.index(0)) // Optional<string, string>
.at("company")
.at("address")
.at("street")
.at("name")
.nonNullable()
.compose(StringOptic.index(0))

const capitalize = (s: string): string => s.toUpperCase()

Expand Down

0 comments on commit d402495

Please sign in to comment.