Files
effect-lens/packages/effect-lens/src/Lens.ts
T
Julien Valverdé 279c0f4a0c
Lint / lint (push) Failing after 9s
Refactor
2026-05-20 18:11:13 +02:00

920 lines
37 KiB
TypeScript

import { Array, Chunk, type Context, Effect, Function, identity, type ManagedRuntime, Option, Pipeable, Predicate, PubSub, Readable, Ref, type Runtime, Stream, type SubscriptionRef, type SynchronizedRef } from "effect"
import type { NoSuchElementException } from "effect/Cause"
import * as Subscribable from "./Subscribable.js"
export const LensTypeId: unique symbol = Symbol.for("@effect-fc/Lens/Lens")
export type LensTypeId = typeof LensTypeId
/**
* A bidirectional view into some shared state that exposes:
*
* 1. a `get` effect for reading the current value of type `A`,
* 2. a `changes` stream that emits every subsequent update to `A`, and
* 3. a `modify` effect that can transform the current value.
*/
export interface Lens<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
extends Subscribable.Subscribable<A, ER, RR> {
readonly [LensTypeId]: LensTypeId
readonly modifyEffect: <B, E1 = never, R1 = never>(
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.Effect<B, ER | EW | E1, RR | RW | R1>
}
/**
* Checks whether a value is a `Lens`.
*/
export const isLens = (u: unknown): u is Lens<unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, LensTypeId)
export const LensWithInternalsTypeId: unique symbol = Symbol.for("@effect-fc/Lens/LensWithInternals")
export type LensWithInternalsTypeId = typeof LensWithInternalsTypeId
export interface LensWithInternals<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
extends Lens<A, ER, EW, RR, RW> {
readonly [LensWithInternalsTypeId]: LensWithInternalsTypeId
readonly update: (a: A) => Effect.Effect<void, EW, RW>
readonly withLock: <A1, E1, R1>(self: Effect.Effect<A1, E1, R1>) => Effect.Effect<A1, E1, R1>
}
export const isLensWithInternals = (u: unknown): u is LensWithInternals<unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, LensWithInternalsTypeId)
export const asLensWithInternals = <A, ER, EW, RR, RW>(
lens: Lens<A, ER, EW, RR, RW>
): Effect.Effect<LensWithInternals<A, ER, EW, RR, RW>, never, never> => isLensWithInternals(lens)
? Effect.succeed(lens as LensWithInternals<A, ER, EW, RR, RW>)
: Effect.die("Not a 'LensWithInternals'.")
export declare namespace LensImpl {
export interface Source<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never> {
readonly get: Effect.Effect<A, ER, RR>,
readonly changes: Stream.Stream<A, ER, RR>,
readonly update: (a: A) => Effect.Effect<void, EW, RW>,
readonly withLock: <A1, E1, R1>(self: Effect.Effect<A1, E1, R1>) => Effect.Effect<A1, E1, R1>,
}
}
export class LensImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
extends Pipeable.Class() implements LensWithInternals<A, ER, EW, RR, RW> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [LensTypeId]: LensTypeId = LensTypeId
readonly [LensWithInternalsTypeId]: LensWithInternalsTypeId = LensWithInternalsTypeId
readonly get: Effect.Effect<A, ER, RR>
readonly changes: Stream.Stream<A, ER, RR>
readonly update: (a: A) => Effect.Effect<void, EW, RW>
readonly withLock: <A1, E1, R1>(self: Effect.Effect<A1, E1, R1>) => Effect.Effect<A1, E1, R1>
constructor(
source: LensImpl.Source<A, ER, EW, RR, RW>
) {
super()
this.get = source.get
this.changes = source.changes
this.update = source.update
this.withLock = source.withLock
}
modifyEffect = modifyEffect
}
function modifyEffect<A, B, ER = never, EW = never, E1 = never, RR = never, RW = never, R1 = never>(
this: LensWithInternals<A, ER, EW, RR, RW>,
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>,
): Effect.Effect<B, ER | EW | E1, RR | RW | R1> {
return this.withLock(Effect.flatMap(
this.get,
a => Effect.flatMap(f(a), ([b, next]) => Effect.as(this.update(next), b),
)))
}
/**
* Creates a `Lens` by supplying how to read the current value, observe changes, and apply transformations.
*/
export const make = <A, ER, EW, RR, RW>(
source: LensImpl.Source<A, ER, EW, RR, RW>
): Lens<A, ER, EW, RR, RW> => new LensImpl(source)
export class LensLazyImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
extends Pipeable.Class() implements LensWithInternals<A, ER, EW, RR, RW> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [LensTypeId]: LensTypeId = LensTypeId
readonly [LensWithInternalsTypeId]: LensWithInternalsTypeId = LensWithInternalsTypeId
constructor(
readonly source: LensImpl.Source<A, ER, EW, RR, RW>
) {
super()
}
get get() { return this.source.get }
get changes() { return this.source.changes }
get update() { return this.source.update }
get withLock() { return this.source.withLock }
modifyEffect = modifyEffect
}
/**
* Creates a `Lens` by supplying how to read the current value, observe changes, and apply transformations.
*/
export const makeLazy = <A, ER, EW, RR, RW>(
source: LensImpl.Source<A, ER, EW, RR, RW>
): Lens<A, ER, EW, RR, RW> => new LensLazyImpl(source)
export declare namespace SynchronizedRefLensImpl {
export interface SynchronizedRefWithInternals<in out A>
extends SynchronizedRef.SynchronizedRef<A> {
readonly ref: Ref.Ref<A>
readonly withLock: <A1, E1, R1>(self: Effect.Effect<A1, E1, R1>) => Effect.Effect<A1, E1, R1>
}
}
export class SynchronizedRefLensImpl<in out A>
extends Pipeable.Class() implements LensWithInternals<A, never, never, never, never> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [LensTypeId]: LensTypeId = LensTypeId
readonly [LensWithInternalsTypeId]: LensWithInternalsTypeId = LensWithInternalsTypeId
readonly ref: SynchronizedRefLensImpl.SynchronizedRefWithInternals<A>
constructor(
ref: SynchronizedRef.SynchronizedRef<A>
) {
super()
this.ref = ref as SynchronizedRefLensImpl.SynchronizedRefWithInternals<A>
}
get get() { return this.ref.get }
get changes() { return Stream.unwrap(Effect.map(this.ref.get, Stream.make)) }
update(a: A) { return Ref.set(this.ref.ref, a) }
get withLock() { return this.ref.withLock }
modifyEffect = modifyEffect
}
/**
* Creates a `Lens` that proxies a `SynchronizedRef`.
*
* Note: since `SynchronizedRef` does not provide any kind of reactivity mechanism, the produced `Lens` will be non-reactive.
* This means its `changes` stream will only emit the current value once when evaluated and nothing else.
*/
export const fromSynchronizedRef = <A>(
ref: SynchronizedRef.SynchronizedRef<A>
): Lens<A, never, never, never, never> => new SynchronizedRefLensImpl(ref)
export declare namespace SubscriptionRefLensImpl {
export interface SubscriptionRefWithInternals<in out A>
extends SubscriptionRef.SubscriptionRef<A> {
readonly ref: Ref.Ref<A>
readonly pubsub: PubSub.PubSub<A>
readonly semaphore: Effect.Semaphore
}
}
export class SubscriptionRefLensImpl<in out A>
extends Pipeable.Class() implements LensWithInternals<A, never, never, never, never> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [LensTypeId]: LensTypeId = LensTypeId
readonly [LensWithInternalsTypeId]: LensWithInternalsTypeId = LensWithInternalsTypeId
readonly ref: SubscriptionRefLensImpl.SubscriptionRefWithInternals<A>
constructor(
ref: SubscriptionRef.SubscriptionRef<A>
) {
super()
this.ref = ref as SubscriptionRefLensImpl.SubscriptionRefWithInternals<A>
}
get get() { return this.ref.get }
get changes() { return this.ref.changes }
update(a: A) {
return Effect.zipLeft(
Ref.set(this.ref.ref, a),
PubSub.publish(this.ref.pubsub, a),
)
}
get withLock() { return this.ref.semaphore.withPermits(1) }
modifyEffect = modifyEffect
}
/**
* Creates a `Lens` that proxies a `SubscriptionRef`.
*/
export const fromSubscriptionRef = <A>(
ref: SubscriptionRef.SubscriptionRef<A>
): Lens<A, never, never, never, never> => new SubscriptionRefLensImpl(ref)
/**
* Flattens an effectful `Lens`.
*/
export const unwrap = <A, ER, EW, RR, RW, E1, R1>(
effect: Effect.Effect<Lens<A, ER, EW, RR, RW>, E1, R1>
): Lens<A, ER | E1, EW | E1, RR | R1, RW | R1> => make({
get: Effect.flatMap(effect, l => l.get),
changes: Stream.unwrap(Effect.map(effect, l => l.changes)),
update: a => Effect.flatMap(
effect,
l => Effect.flatMap(asLensWithInternals(l), l => l.update(a))
),
withLock: <A2, E2, R2>(
effect2: Effect.Effect<A2, E2, R2>
): Effect.Effect<A2, E1 | E2, R1 | R2> => Effect.flatMap(
effect,
l => Effect.flatMap(asLensWithInternals(l), l2 => l2.withLock(effect2)),
),
})
/**
* Derives a new `Lens` by applying synchronous getters and setters over the focused value.
*/
export const map: {
<A, ER, EW, RR, RW, B>(
self: Lens<A, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => B,
update: (a: NoInfer<A>, b: B) => NoInfer<A>,
): Lens<B, ER, EW, RR, RW>
<A, ER, EW, RR, RW, B>(
get: (a: NoInfer<A>) => B,
update: (a: NoInfer<A>, b: B) => NoInfer<A>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<B, ER, EW, RR, RW>
} = Function.dual(3, <A, ER, EW, RR, RW, B>(
self: Lens<A, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => B,
update: (a: NoInfer<A>, b: B) => NoInfer<A>,
): Lens<B, ER, EW, RR, RW> => makeLazy({
get get() { return Effect.map(self.get, get) },
get changes() { return Stream.map(self.changes, get) },
modify: <C, E1 = never, R1 = never>(
f: (b: B) => Effect.Effect<readonly [C, B], E1, R1>
) => self.modify(a =>
Effect.flatMap(f(get(a)), ([c, next]) => Effect.succeed([c, set(a, next)]))
),
}))
/**
* Derives a new `Lens` by applying effectful getters and setters over the focused value.
*/
export const mapEffect: {
<A, ER, EW, RR, RW, B, EGet = never, RGet = never, ESet = never, RSet = never>(
self: Lens<A, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>,
set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>,
): Lens<B, ER | EGet, EW | ESet, RR | RGet, RW | RSet>
<A, ER, EW, RR, RW, B, EGet = never, RGet = never, ESet = never, RSet = never>(
get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>,
set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<B, ER | EGet, EW | ESet, RR | RGet, RW | RSet>
} = Function.dual(3, <A, ER, EW, RR, RW, B, EGet = never, RGet = never, ESet = never, RSet = never>(
self: Lens<A, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>,
set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>,
): Lens<B, ER | EGet, EW | ESet, RR | RGet, RW | RSet> => make({
get get() { return Effect.flatMap(self.get, get) },
get changes() { return Stream.mapEffect(self.changes, get) },
modify: <C, E1 = never, R1 = never>(
f: (b: B) => Effect.Effect<readonly [C, B], E1, R1>
) => self.modify(a => Effect.flatMap(
get(a),
b => Effect.flatMap(
f(b),
([c, bNext]) => Effect.flatMap(
set(a, bNext),
nextA => Effect.succeed([c, nextA] as const),
),
)
)),
}))
/**
* Derives a new `Lens` by applying synchronous getters and setters over the value inside an `Option`.
*
* Similar to `Option.map`, this preserves the `Option` structure:
* - If the `Option` is `Some(a)`, applies the getter and setter to the inner value
* - If the `Option` is `None`, it remains `None`
*/
export const mapOption: {
<A, ER, EW, RR, RW, B>(
self: Lens<Option.Option<A>, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => B,
set: (a: NoInfer<A>, b: B) => NoInfer<A>,
): Lens<Option.Option<B>, ER, EW, RR, RW>
<A, ER, EW, RR, RW, B>(
get: (a: NoInfer<A>) => B,
set: (a: NoInfer<A>, b: B) => NoInfer<A>,
): (self: Lens<Option.Option<A>, ER, EW, RR, RW>) => Lens<Option.Option<B>, ER, EW, RR, RW>
} = Function.dual(3, <A, ER, EW, RR, RW, B>(
self: Lens<Option.Option<A>, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => B,
set: (a: NoInfer<A>, b: B) => NoInfer<A>,
): Lens<Option.Option<B>, ER, EW, RR, RW> => map(
self,
Option.map(get),
(opt, newOpt) => Option.match(opt, {
onSome: a => Option.map(newOpt, b => set(a, b)),
onNone: () => Option.none(),
}),
))
/**
* Derives a new `Lens` by applying effectful getters and setters over the value inside an `Option`.
*
* Similar to `Option.map`, this preserves the `Option` structure:
* - If the `Option` is `Some(a)`, applies the effectful getter and setter to the inner value
* - If the `Option` is `None`, it remains `None`
*/
export const mapOptionEffect: {
<A, ER, EW, RR, RW, B, EGet = never, RGet = never, ESet = never, RSet = never>(
self: Lens<Option.Option<A>, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>,
set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>,
): Lens<Option.Option<B>, ER | EGet, EW | ESet, RR | RGet, RW | RSet>
<A, ER, EW, RR, RW, B, EGet = never, RGet = never, ESet = never, RSet = never>(
get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>,
set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>,
): (self: Lens<Option.Option<A>, ER, EW, RR, RW>) => Lens<Option.Option<B>, ER | EGet, EW | ESet, RR | RGet, RW | RSet>
} = Function.dual(3, <A, ER, EW, RR, RW, B, EGet = never, RGet = never, ESet = never, RSet = never>(
self: Lens<Option.Option<A>, ER, EW, RR, RW>,
get: (a: NoInfer<A>) => Effect.Effect<B, EGet, RGet>,
set: (a: NoInfer<A>, b: B) => Effect.Effect<NoInfer<A>, ESet, RSet>,
): Lens<Option.Option<B>, ER | EGet, EW | ESet, RR | RGet, RW | RSet> => mapEffect(
self,
Option.match({
onSome: a => Effect.map(get(a), Option.some),
onNone: () => Effect.succeed(Option.none()),
}),
(opt, newOpt) => Option.match(opt, {
onSome: a => Option.match(newOpt, {
onSome: b => Effect.map(set(a, b), Option.some),
onNone: () => Effect.succeed(Option.none()),
}),
onNone: () => Effect.succeed(Option.none()),
}),
))
/**
* Allows transforming only the `changes` stream of a `Lens` while keeping the focus type intact.
*/
export const mapStream: {
<A, ER, EW, RR, RW>(
self: Lens<A, ER, EW, RR, RW>,
f: (changes: Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>) => Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>,
): Lens<A, ER, EW, RR, RW>
<A, ER, EW, RR, RW>(
f: (changes: Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>) => Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(
self: Lens<A, ER, EW, RR, RW>,
f: (changes: Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>) => Stream.Stream<NoInfer<A>, NoInfer<ER>, NoInfer<RR>>,
): Lens<A, ER, EW, RR, RW> => make({
get get() { return self.get },
get changes() { return f(self.changes) },
get modify() { return self.modify },
}))
/**
* Transforms read errors of a `Lens`.
*
* Applies to `get` and `changes` while leaving `modify` unchanged.
*/
export const mapErrorRead: {
<A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => E2,
): Lens<A, E2, EW, RR, RW>
<A, ER, EW, RR, RW, E2>(
f: (error: NoInfer<ER>) => E2,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, EW, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => E2,
): Lens<A, E2, EW, RR, RW> => make({
get get() { return Effect.mapError(self.get, f) },
get changes() { return Stream.mapError(self.changes, f) },
get modify() { return self.modify as any },
}))
/**
* Transforms modify errors of a `Lens`.
*
* Applies to the `modify` effect. Since `modify` may also fail with errors coming from the
* user-supplied callback, the handler receives `unknown`.
*/
export const mapErrorWrite: {
<A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => E2,
): Lens<A, ER, E2, RR, RW>
<A, ER, EW, RR, RW, E2>(
f: (error: unknown) => E2,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, E2, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => E2,
): Lens<A, ER, E2, RR, RW> => make({
get get() { return self.get },
get changes() { return self.changes },
modify: <B, E1 = never, R1 = never>(
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.mapError(self.modify(g), f),
}))
/**
* Transforms all errors of a `Lens`.
*
* Applies to `get`, `changes`, and `modify`. Since `modify` may also fail with errors coming
* from the user-supplied callback, the handler receives `unknown`.
*/
export const mapError: {
<A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => E2,
): Lens<A, E2, E2, RR, RW>
<A, ER, EW, RR, RW, E2>(
f: (error: unknown) => E2,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, E2, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => E2,
): Lens<A, E2, E2, RR, RW> => make({
get get() { return Effect.mapError(self.get, f) },
get changes() { return Stream.mapError(self.changes, f) },
modify: <B, E1 = never, R1 = never>(
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.mapError(self.modify(g), f),
}))
/**
* Recovers from read failures of a `Lens`.
*
* Applies to `get` and `changes` while leaving `modify` unchanged.
*/
export const catchAllRead: {
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => Subscribable.Subscribable<A, E2, R2>,
): Lens<A, E2, EW, RR | R2, RW>
<A, ER, EW, RR, RW, E2, R2>(
f: (error: NoInfer<ER>) => Subscribable.Subscribable<A, E2, R2>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, EW, RR | R2, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => Subscribable.Subscribable<A, E2, R2>,
): Lens<A, E2, EW, RR | R2, RW> => make({
get get() { return Effect.catchAll(self.get, error => f(error).get) },
get changes() { return Stream.catchAll(self.changes, error => f(error).changes) },
get modify() { return self.modify as any },
}))
/**
* Runs an effect when read failures occur.
*
* Applies to `get` and `changes` while leaving `modify` unchanged.
*/
export const tapErrorRead: {
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => Effect.Effect<any, E2, R2>,
): Lens<A, ER | E2, EW, RR | R2, RW>
<A, ER, EW, RR, RW, E2, R2>(
f: (error: NoInfer<ER>) => Effect.Effect<any, E2, R2>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER | E2, EW, RR | R2, RW>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: NoInfer<ER>) => Effect.Effect<any, E2, R2>,
): Lens<A, ER | E2, EW, RR | R2, RW> => make({
get get() { return Effect.tapError(self.get, f) },
get changes() { return Stream.tapError(self.changes, f) },
get modify() { return self.modify as any },
}))
/**
* Runs an effect when modify failures occur.
*
* Applies to the `modify` effect. Since `modify` may also fail with errors coming from the
* user-supplied callback, the handler receives `unknown`.
*/
export const tapErrorWrite: {
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => Effect.Effect<any, E2, R2>,
): Lens<A, ER, EW | E2, RR, RW | R2>
<A, ER, EW, RR, RW, E2, R2>(
f: (error: unknown) => Effect.Effect<any, E2, R2>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW | E2, RR, RW | R2>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => Effect.Effect<any, E2, R2>,
): Lens<A, ER, EW | E2, RR, RW | R2> => make({
get get() { return self.get },
get changes() { return self.changes },
modify: <B, E1 = never, R1 = never>(
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.tapError(self.modify(g), f),
}))
/**
* Runs an effect when any `Lens` failure occurs.
*
* Applies to `get`, `changes`, and `modify`. Since `modify` may also fail with errors coming
* from the user-supplied callback, the handler receives `unknown`.
*/
export const tapError: {
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => Effect.Effect<any, E2, R2>,
): Lens<A, ER | E2, EW | E2, RR | R2, RW | R2>
<A, ER, EW, RR, RW, E2, R2>(
f: (error: unknown) => Effect.Effect<any, E2, R2>,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER | E2, EW | E2, RR | R2, RW | R2>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
f: (error: unknown) => Effect.Effect<any, E2, R2>,
): Lens<A, ER | E2, EW | E2, RR | R2, RW | R2> => make({
get get() { return Effect.tapError(self.get, f) },
get changes() { return Stream.tapError(self.changes, f) },
modify: <B, E1 = never, R1 = never>(
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.tapError(self.modify(g), f),
}))
/**
* Provides a `Context` to a `Lens`, removing it from both the read and write environments.
*/
export const provideContext: {
<A, ER, EW, RR, RW, R2>(
self: Lens<A, ER, EW, RR, RW>,
context: Context.Context<R2>,
): Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>>
<R2>(
context: Context.Context<R2>,
): <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>>
} = Function.dual(2, <A, ER, EW, RR, RW, R2>(
self: Lens<A, ER, EW, RR, RW>,
context: Context.Context<R2>,
): Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>> => make({
get get() { return Effect.provide(self.get, context) },
get changes() { return Stream.provideSomeContext(self.changes, context) },
modify: <B, E1 = never, R1 = never>(
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.provide(self.modify(f), context),
}))
/**
* Provides a `Runtime` or `ManagedRuntime` to a `Lens`, removing it from both the read and write environments.
*
* `ManagedRuntime` may add its construction errors to both the read and write error channels.
*/
export const provideRuntime: {
<R2>(
runtime: Runtime.Runtime<R2>,
): <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>>
<E2, R2>(
managedRuntime: ManagedRuntime.ManagedRuntime<R2, E2>,
): <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER | E2, EW | E2, Exclude<RR, R2>, Exclude<RW, R2>>
<A, ER, EW, RR, RW, R2>(
self: Lens<A, ER, EW, RR, RW>,
runtime: Runtime.Runtime<R2>,
): Lens<A, ER, EW, Exclude<RR, R2>, Exclude<RW, R2>>
<A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
runtime: ManagedRuntime.ManagedRuntime<R2, E2>,
): Lens<A, ER | E2, EW | E2, Exclude<RR, R2>, Exclude<RW, R2>>
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
self: Lens<A, ER, EW, RR, RW>,
runtime: Runtime.Runtime<R2>,
) => make<A, ER | E2, EW | E2, Exclude<RR, R2>, Exclude<RW, R2>>({
get get() { return Effect.provide(self.get, runtime) },
get changes() {
return Stream.unwrap(Effect.map(
Effect.provide(Effect.context<RR>(), runtime),
context => Stream.provideContext(self.changes, context),
))
},
modify: <B, E1 = never, R1 = never>(
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.provide(self.modify(f), runtime),
}))
/**
* Provides a single service to a `Lens`, removing it from both the read and write environments.
*
* This is the `Lens` equivalent of `Effect.provideService`: use it when a lens requires one
* `Context.Tag` and you already have the concrete service value.
*/
export const provideService: {
<A, ER, EW, RR, RW, I, S>(
self: Lens<A, ER, EW, RR, RW>,
tag: Context.Tag<I, S>,
service: NoInfer<S>,
): Lens<A, ER, EW, Exclude<RR, I>, Exclude<RW, I>>
<I, S>(
tag: Context.Tag<I, S>,
service: NoInfer<S>,
): <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW, Exclude<RR, I>, Exclude<RW, I>>
} = Function.dual(3, <A, ER, EW, RR, RW, I, S>(
self: Lens<A, ER, EW, RR, RW>,
tag: Context.Tag<I, S>,
service: NoInfer<S>,
): Lens<A, ER, EW, Exclude<RR, I>, Exclude<RW, I>> => make({
get get() { return Effect.provideService(self.get, tag, service) },
get changes() { return Stream.provideService(self.changes, tag, service) },
modify: <B, E1 = never, R1 = never>(
f: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
) => Effect.provideService(self.modify(f), tag, service),
}))
/**
* Narrows the focus to a field of an object. Replaces the object in an immutable fashion when written to.
*/
export const focusObjectOn: {
<A extends object, ER, EW, RR, RW, K extends keyof A>(
self: Lens<A, ER, EW, RR, RW>,
key: K,
): Lens<A[K], ER, EW, RR, RW>
<A extends object, ER, EW, RR, RW, K extends keyof A>(
key: K,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A[K], ER, EW, RR, RW>
} = Function.dual(2, <A extends object, ER, EW, RR, RW, K extends keyof A>(
self: Lens<A, ER, EW, RR, RW>,
key: K,
): Lens<A[K], ER, EW, RR, RW> => map(
self,
a => a[key],
(a, b) => Object.setPrototypeOf({ ...a, [key]: b }, Object.getPrototypeOf(a)),
))
export declare namespace focusObjectOnWritable {
export type WritableKeys<T> = {
[K in keyof T]-?: IfEquals<
{ [P in K]: T[K] },
{ -readonly [P in K]: T[K] },
K,
never
>
}[keyof T]
type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? A : B
}
/**
* Narrows the focus to a writable field of an object. Mutates the object in place when written to.
*/
export const focusObjectOnWritable: {
<A extends object, ER, EW, RR, RW, K extends focusObjectOnWritable.WritableKeys<A>>(
self: Lens<A, ER, EW, RR, RW>,
key: K,
): Lens<A[K], ER, EW, RR, RW>
<A extends object, ER, EW, RR, RW, K extends focusObjectOnWritable.WritableKeys<A>>(
key: K,
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A[K], ER, EW, RR, RW>
} = Function.dual(2, <A extends object, ER, EW, RR, RW, K extends focusObjectOnWritable.WritableKeys<A>>(
self: Lens<A, ER, EW, RR, RW>,
key: K,
): Lens<A[K], ER, EW, RR, RW> => map(self, a => a[key], (a, b) => { a[key] = b; return a }))
/**
* Narrows the focus to an indexed element of an array. Replaces the array in an immutable fashion when written to.
*/
export const focusArrayAt: {
<A extends readonly any[], ER, EW, RR, RW>(
self: Lens<A, ER, EW, RR, RW>,
index: number,
): Lens<A[number], ER | NoSuchElementException, EW | NoSuchElementException, RR, RW>
<A extends readonly any[], ER, EW, RR, RW>(
index: number
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A[number], ER | NoSuchElementException, EW | NoSuchElementException, RR, RW>
} = Function.dual(2, <A extends readonly any[], ER, EW, RR, RW>(
self: Lens<A, ER, EW, RR, RW>,
index: number,
): Lens<A[number], ER | NoSuchElementException, EW | NoSuchElementException, RR, RW> => mapEffect(
self,
Array.get(index),
(a, b) => Array.replaceOption(a, index, b) as any,
))
/**
* Narrows the focus to an indexed element of a mutable array. Mutates the array in place when written to.
*/
export const focusMutableArrayAt: {
<A, ER, EW, RR, RW>(
self: Lens<A[], ER, EW, RR, RW>,
index: number,
): Lens<A, ER | NoSuchElementException, EW | NoSuchElementException, RR, RW>
<A, ER, EW, RR, RW>(
index: number
): (self: Lens<A[], ER, EW, RR, RW>) => Lens<A, ER | NoSuchElementException, EW | NoSuchElementException, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(
self: Lens<A[], ER, EW, RR, RW>,
index: number,
): Lens<A, ER | NoSuchElementException, EW | NoSuchElementException, RR, RW> => mapEffect(
self,
Array.get(index),
(a, b) => Effect.flatMap(
Array.get(a, index),
() => Effect.as(Effect.sync(() => { a[index] = b }), a),
),
))
/**
* Narrows the focus to an indexed element of a readonly tuple. Replaces the tuple in an immutable fashion when written to.
*/
export const focusTupleAt: {
<T extends readonly [any, ...any[]], ER, EW, RR, RW, I extends number>(
self: Lens<T, ER, EW, RR, RW>,
index: I,
): Lens<T[I], ER, EW, RR, RW>
<T extends readonly [any, ...any[]], ER, EW, RR, RW, I extends number>(
index: I
): (self: Lens<T, ER, EW, RR, RW>) => Lens<T[I], ER, EW, RR, RW>
} = Function.dual(2, <T extends readonly [any, ...any[]], ER, EW, RR, RW, I extends number>(
self: Lens<T, ER, EW, RR, RW>,
index: I,
): Lens<T[I], ER, EW, RR, RW> => map(
self,
Array.unsafeGet(index),
(a, b) => Array.replace(a, index, b) as any,
))
/**
* Narrows the focus to an indexed element of a mutable tuple. Mutates the tuple in place when written to.
*/
export const focusMutableTupleAt: {
<T extends [any, ...any[]], ER, EW, RR, RW, I extends number>(
self: Lens<T, ER, EW, RR, RW>,
index: I,
): Lens<T[I], ER, EW, RR, RW>
<T extends [any, ...any[]], ER, EW, RR, RW, I extends number>(
index: I
): (self: Lens<T, ER, EW, RR, RW>) => Lens<T[I], ER, EW, RR, RW>
} = Function.dual(2, <T extends [any, ...any[]], ER, EW, RR, RW, I extends number>(
self: Lens<T, ER, EW, RR, RW>,
index: I,
): Lens<T[I], ER, EW, RR, RW> => map(
self,
Array.unsafeGet(index),
(a, b) => { a[index] = b; return a },
))
/**
* Narrows the focus to an indexed element of `Chunk`. Replaces the `Chunk` in an immutable fashion when written to.
*/
export const focusChunkAt: {
<A, ER, EW, RR, RW>(
self: Lens<Chunk.Chunk<A>, ER, EW, RR, RW>,
index: number,
): Lens<A, ER | NoSuchElementException, EW, RR, RW>
<A, ER, EW, RR, RW>(
index: number
): (self: Lens<Chunk.Chunk<A>, ER, EW, RR, RW>) => Lens<A, ER | NoSuchElementException, EW, RR, RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(
self: Lens<Chunk.Chunk<A>, ER, EW, RR, RW>,
index: number,
): Lens<A, ER | NoSuchElementException, EW, RR, RW> => mapEffect(
self,
Chunk.get(index),
(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,
identity,
(a, b) => Effect.map(a, () => Option.some(b)),
)
/**
* Reads the current value from a `Lens`.
*/
export const get = <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>): Effect.Effect<A, ER, RR> => self.get
/**
* Sets the value of a `Lens`.
*/
export const set: {
<A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<void, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) =>
self.modify<void, never, never>(() => Effect.succeed([void 0, value] as const)),
)
/**
* Sets a `Lens` to a new value and returns the previous value.
*/
export const getAndSet: {
<A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) =>
self.modify<A, never, never>(a => Effect.succeed([a, value] as const)),
)
/**
* Applies a synchronous transformation to the value of a `Lens`, discarding the previous value.
*/
export const update: {
<A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<void, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) =>
self.modify<void, never, never>(a => Effect.succeed([void 0, f(a)] as const)),
)
/**
* Applies an effectful transformation to the value of a `Lens`, discarding the previous value.
*/
export const updateEffect: {
<A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<void, ER | EW | E, RR | RW | R>
<A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<void, ER | EW | E, RR | RW | R>
} = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) =>
self.modify<void, E, R>(a => Effect.flatMap(
f(a),
next => Effect.succeed([void 0, next] as const),
)),
)
/**
* Applies a synchronous transformation the value of a `Lens` while returning the previous value.
*/
export const getAndUpdate: {
<A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) =>
self.modify<A, never, never>(a => Effect.succeed([a, f(a)] as const)),
)
/**
* Applies an effectful transformation the value of a `Lens` while returning the previous value.
*/
export const getAndUpdateEffect: {
<A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW | E, RR | RW | R>
<A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, ER | EW | E, RR | RW | R>
} = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) =>
self.modify<A, E, R>(a => Effect.flatMap(
f(a),
next => Effect.succeed([a, next] as const)
)),
)
/**
* Sets the value of a `Lens` and returns the new value.
*/
export const setAndGet: {
<A, ER, EW, RR, RW>(value: A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, value: A) =>
self.modify<A, never, never>(() => Effect.succeed([value, value] as const)),
)
/**
* Applies a synchronous update the value of a `Lens` and returns the new value.
*/
export const updateAndGet: {
<A, ER, EW, RR, RW>(f: (a: A) => A): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW, RR | RW>
<A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A): Effect.Effect<A, ER | EW, RR | RW>
} = Function.dual(2, <A, ER, EW, RR, RW>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => A) =>
self.modify<A, never, never>(a => {
const next = f(a)
return Effect.succeed([next, next] as const)
}),
)
/**
* Applies an effectful update to the value of a `Lens` and returns the new value.
*/
export const updateAndGetEffect: {
<A, ER, EW, RR, RW, E, R>(f: (a: A) => Effect.Effect<A, E, R>): (self: Lens<A, ER, EW, RR, RW>) => Effect.Effect<A, ER | EW | E, RR | RW | R>
<A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, ER | EW | E, RR | RW | R>
} = Function.dual(2, <A, ER, EW, RR, RW, E, R>(self: Lens<A, ER, EW, RR, RW>, f: (a: A) => Effect.Effect<A, E, R>) =>
self.modify<A, E, R>(a => Effect.flatMap(
f(a),
next => Effect.succeed([next, next] as const),
)),
)