Add focusOption
This commit is contained in:
@@ -165,6 +165,7 @@ Currently available:
|
|||||||
| `focusTupleAt` | Focuses to an indexed entry of a readonly tuple. Replaces the parent tuple immutably when writing to the focused index | Immutable | |
|
| `focusTupleAt` | Focuses to an indexed entry of a readonly tuple. Replaces the parent tuple immutably when writing to the focused index | Immutable | |
|
||||||
| `focusMutableTupleAt` | Focuses to an indexed entry of a mutable tuple. Mutates the parent tuple in place at the focused index | Mutable | Type-safe: will not allow you to mutate `readonly` tuples |
|
| `focusMutableTupleAt` | Focuses to an indexed entry of a mutable tuple. Mutates the parent tuple in place at the focused index | Mutable | Type-safe: will not allow you to mutate `readonly` tuples |
|
||||||
| `focusChunkAt` | Focuses to an indexed entry of a `Chunk`. Replaces the parent `Chunk` immutably when writing to the focused element | Immutable | |
|
| `focusChunkAt` | Focuses to an indexed entry of a `Chunk`. Replaces the parent `Chunk` immutably when writing to the focused element | Immutable | |
|
||||||
|
| `focusOption` | Focuses to the value inside an `Option`. Wraps writes back into `Option.some` | Immutable | Reading or writing fails with `NoSuchElementException` when the parent option is `None` |
|
||||||
|
|
||||||
Also more to come!
|
Also more to come!
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,47 @@ describe("Lens", () => {
|
|||||||
expect(Chunk.toReadonlyArray(updated)).toEqual([1, 2, 99])
|
expect(Chunk.toReadonlyArray(updated)).toEqual([1, 2, 99])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("focusOption reads and writes the inner Some value", async () => {
|
||||||
|
const result = await Effect.runPromise(
|
||||||
|
Effect.flatMap(
|
||||||
|
SubscriptionRef.make<Option.Option<number>>(Option.some(42)),
|
||||||
|
parent => {
|
||||||
|
const lens = Lens.focusOption(Lens.fromSubscriptionRef(parent))
|
||||||
|
return Effect.flatMap(
|
||||||
|
Lens.get(lens),
|
||||||
|
value => Effect.flatMap(
|
||||||
|
Lens.set(lens, 100),
|
||||||
|
() => Effect.map(parent.get, parentValue => [value, parentValue] as const),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result[0]).toBe(42)
|
||||||
|
expect(result[1]).toEqual(Option.some(100))
|
||||||
|
})
|
||||||
|
|
||||||
|
test("focusOption fails when the parent option is None", async () => {
|
||||||
|
const result = await Effect.runPromise(
|
||||||
|
Effect.flatMap(
|
||||||
|
SubscriptionRef.make<Option.Option<number>>(Option.none()),
|
||||||
|
parent => {
|
||||||
|
const lens = Lens.focusOption(Lens.fromSubscriptionRef(parent))
|
||||||
|
return Effect.all([
|
||||||
|
Effect.either(Lens.get(lens)),
|
||||||
|
Effect.either(Lens.set(lens, 100)),
|
||||||
|
parent.get,
|
||||||
|
] as const)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result[0]._tag).toBe("Left")
|
||||||
|
expect(result[1]._tag).toBe("Left")
|
||||||
|
expect(result[2]).toEqual(Option.none())
|
||||||
|
})
|
||||||
|
|
||||||
// test("changes stream emits updates when lens mutates state", async () => {
|
// test("changes stream emits updates when lens mutates state", async () => {
|
||||||
// const events = await Effect.runPromise(
|
// const events = await Effect.runPromise(
|
||||||
// Effect.flatMap(
|
// Effect.flatMap(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Array, Chunk, Effect, Function, Option, Pipeable, Predicate, Readable, Stream, type SubscriptionRef, type SynchronizedRef } from "effect"
|
import { Array, Chunk, Effect, Function, Option, Pipeable, Predicate, Readable, Stream, type SubscriptionRef, type SynchronizedRef } from "effect"
|
||||||
|
import * as Cause from "effect/Cause"
|
||||||
import type { NoSuchElementException } from "effect/Cause"
|
import type { NoSuchElementException } from "effect/Cause"
|
||||||
import * as Subscribable from "./Subscribable.js"
|
import * as Subscribable from "./Subscribable.js"
|
||||||
|
|
||||||
@@ -422,6 +423,30 @@ export const focusChunkAt: {
|
|||||||
(a, b) => Effect.succeed(Chunk.replace(a, index, b))),
|
(a, b) => Effect.succeed(Chunk.replace(a, index, b))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Narrows the focus to the value inside an `Option`.
|
||||||
|
*
|
||||||
|
* Reading or writing through this lens fails with `NoSuchElementException` when the parent option is `None`.
|
||||||
|
* Writing wraps the new focused value back into `Option.some`.
|
||||||
|
*/
|
||||||
|
export const focusOption: {
|
||||||
|
<A, ER, EW, RR, RW>(
|
||||||
|
self: Lens<Option.Option<A>, ER, EW, RR, RW>,
|
||||||
|
): Lens<A, ER | NoSuchElementException, EW | NoSuchElementException, RR, RW>
|
||||||
|
} = <A, ER, EW, RR, RW>(
|
||||||
|
self: Lens<Option.Option<A>, ER, EW, RR, RW>,
|
||||||
|
): Lens<A, ER | NoSuchElementException, EW | NoSuchElementException, RR, RW> => mapEffect(
|
||||||
|
self,
|
||||||
|
option => Option.match(option, {
|
||||||
|
onSome: value => Effect.succeed(value),
|
||||||
|
onNone: () => Effect.fail(new Cause.NoSuchElementException()),
|
||||||
|
}),
|
||||||
|
(option, value) => Option.match(option, {
|
||||||
|
onSome: () => Effect.succeed(Option.some(value)),
|
||||||
|
onNone: () => Effect.fail(new Cause.NoSuchElementException()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the current value from a `Lens`.
|
* Reads the current value from a `Lens`.
|
||||||
|
|||||||
Reference in New Issue
Block a user