3.8 KiB
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
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
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
Lens.Lens<
A, // focused value
ER, // read errors
EW, // write errors
RR, // read requirements
RW // write requirements
>
Constructors
Lens.fromSubscriptionRefcreates a reactive Lens.Lens.fromSynchronizedRefcreates a non-reactive Lens whose stream emits its value when evaluated.Lens.fromRefcreates a non-reactive Lens and returns it in an Effect because it allocates a semaphore.Lens.makecreates a Lens from customget,changes,commit, andlockoperations.Lens.unwrapflattens an effect that produces a Lens.
Focusing
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:
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,setAndGetmodify,modifyEffectupdate,updateEffectgetAndUpdate,getAndUpdateEffectupdateAndGet,updateAndGetEffectmodifySome,modifySomeEffectgetAndUpdateSome,getAndUpdateSomeEffectupdateSome,updateSomeEffectupdateSomeAndGet,updateSomeAndGetEffect
Conditional updates use Option.none() to skip both the write and change notification:
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:
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, 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.