Files
effect-lens/packages/effect-lens-next/README.md
T
Julien Valverdé e15802976d
Lint / lint (push) Successful in 16s
Add additional error handling primitives for Subscribable
2026-06-21 23:32:29 +02:00

3.9 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.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

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, 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:

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 value transforms, error recovery (catch, catchCause, orElse, orElseSucceed, and retry), error inspection, 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.