138 lines
3.9 KiB
Markdown
138 lines
3.9 KiB
Markdown
# Effect Lens Next
|
|
|
|
An effectful Lens for Effect v4.
|
|
|
|
`effect-lens-next` provides a bidirectional view into shared state. A Lens can read, observe, and atomically update a value while focusing on a nested part of a larger structure.
|
|
|
|
## Install
|
|
|
|
```sh
|
|
npm install effect@4.0.0-beta.85 effect-lens-next
|
|
```
|
|
|
|
This release targets Effect `4.0.0-beta.85` exactly while Effect v4 is in beta.
|
|
|
|
## Quickstart
|
|
|
|
```ts
|
|
import { Effect, Stream, SubscriptionRef } from "effect"
|
|
import { Lens } from "effect-lens-next"
|
|
|
|
const program = Effect.gen(function*() {
|
|
const state = yield* SubscriptionRef.make({ count: 0, label: "Counter" })
|
|
|
|
const count = state.pipe(
|
|
Lens.fromSubscriptionRef,
|
|
Lens.focusObjectOn("count")
|
|
)
|
|
|
|
yield* Lens.update(count, (value) => value + 1)
|
|
const current = yield* Lens.get(count)
|
|
|
|
yield* count.changes.pipe(
|
|
Stream.take(1),
|
|
Stream.runForEach((value) => Effect.log(`count: ${value}`))
|
|
)
|
|
|
|
return current
|
|
})
|
|
```
|
|
|
|
## Lens Type
|
|
|
|
```ts
|
|
Lens.Lens<
|
|
A, // focused value
|
|
ER, // read errors
|
|
EW, // write errors
|
|
RR, // read requirements
|
|
RW // write requirements
|
|
>
|
|
```
|
|
|
|
## Constructors
|
|
|
|
- `Lens.fromSubscriptionRef` creates a reactive Lens.
|
|
- `Lens.fromSynchronizedRef` creates a non-reactive Lens whose stream emits its value when evaluated.
|
|
- `Lens.fromRef` creates a non-reactive Lens and returns it in an Effect because it allocates a semaphore.
|
|
- `Lens.make` creates a Lens from custom `get`, `changes`, `commit`, and `lock` operations.
|
|
- `Lens.unwrap` flattens an effect that produces a Lens.
|
|
|
|
## Focusing
|
|
|
|
```ts
|
|
import { Effect, SubscriptionRef } from "effect"
|
|
import { Lens } from "effect-lens-next"
|
|
|
|
const program = Effect.gen(function*() {
|
|
const state = yield* SubscriptionRef.make({
|
|
users: [{ name: "Ada", score: 10 }]
|
|
})
|
|
|
|
const score = state.pipe(
|
|
Lens.fromSubscriptionRef,
|
|
Lens.focusObjectOn("users"),
|
|
Lens.focusArrayAt(0),
|
|
Lens.focusObjectOn("score")
|
|
)
|
|
|
|
yield* Lens.set(score, 11)
|
|
})
|
|
```
|
|
|
|
Available focus operations include object fields, mutable object fields, arrays, mutable arrays, tuples, mutable tuples, `Chunk` values, and `Option` values. Safe indexed operations fail with `Cause.NoSuchElementError` when the index is absent.
|
|
|
|
Effect v4 no longer treats `Option` as an Effect. Custom effectful mappings should convert optional values explicitly:
|
|
|
|
```ts
|
|
import { Array, Effect } from "effect"
|
|
|
|
Lens.mapEffect(
|
|
(values: ReadonlyArray<number>) => Effect.fromOption(Array.get(values, 0)),
|
|
(values, value) => Effect.fromOption(Array.replace(values, 0, value))
|
|
)
|
|
```
|
|
|
|
## Updating
|
|
|
|
The update API follows Effect v4's `SubscriptionRef` conventions:
|
|
|
|
- `get`, `set`, `getAndSet`, `setAndGet`
|
|
- `modify`, `modifyEffect`
|
|
- `update`, `updateEffect`
|
|
- `getAndUpdate`, `getAndUpdateEffect`
|
|
- `updateAndGet`, `updateAndGetEffect`
|
|
- `modifySome`, `modifySomeEffect`
|
|
- `getAndUpdateSome`, `getAndUpdateSomeEffect`
|
|
- `updateSome`, `updateSomeEffect`
|
|
- `updateSomeAndGet`, `updateSomeAndGetEffect`
|
|
|
|
Conditional updates use `Option.none()` to skip both the write and change notification:
|
|
|
|
```ts
|
|
import { Effect, Option } from "effect"
|
|
|
|
yield* Lens.updateSome(lens, (value) =>
|
|
value < 10 ? Option.some(value + 1) : Option.none()
|
|
)
|
|
|
|
yield* Lens.updateSomeEffect(lens, (value) =>
|
|
Effect.succeed(value < 10 ? Option.some(value + 1) : Option.none())
|
|
)
|
|
```
|
|
|
|
## Subscribable
|
|
|
|
Effect v4 removed its `Readable` and `Subscribable` modules. This package provides a small compatible abstraction with `get` and `changes` properties:
|
|
|
|
```ts
|
|
import { Lens, Subscribable } from "effect-lens-next"
|
|
|
|
const users = Lens.fromSubscriptionRef(usersRef)
|
|
const count = Subscribable.focusArrayLength(users)
|
|
|
|
const current = yield* count.get
|
|
```
|
|
|
|
The module includes `make`, `map`, `mapEffect`, `mapError`, `tapError`, `unwrap`, and the same readonly focus helpers used by Lens. A `SubscriptionRef` can first be converted with `Lens.fromSubscriptionRef`, since every Lens is already a Subscribable.
|