import { Array, Chunk, Effect, Function, Pipeable, Predicate, Readable, Stream, Subscribable, type SubscriptionRef } from "effect" import type { NoSuchElementException } from "effect/Cause" export const LensTypeId: unique symbol = Symbol.for("@effect-fc/Lens/Lens") export type LensTypeId = typeof LensTypeId export interface Lens extends Subscribable.Subscribable { readonly [LensTypeId]: LensTypeId readonly modify: ( f: (a: A) => Effect.Effect ) => Effect.Effect } export class LensImpl extends Pipeable.Class() implements Lens { readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId readonly [LensTypeId]: LensTypeId = LensTypeId constructor( readonly get: Effect.Effect, readonly changes: Stream.Stream, readonly modify: ( f: (a: A) => Effect.Effect ) => Effect.Effect, ) { super() } } export const isLens = (u: unknown): u is Lens => Predicate.hasProperty(u, LensTypeId) export const make = ( options: { readonly get: Effect.Effect readonly changes: Stream.Stream } & ( | { readonly modify: ( f: (a: A) => Effect.Effect ) => Effect.Effect } | { readonly set: (a: A) => Effect.Effect } ) ): Lens => new LensImpl( options.get, options.changes, Predicate.hasProperty(options, "modify") ? options.modify : ( f: (a: A) => Effect.Effect ) => Effect.flatMap( options.get, a => Effect.flatMap(f(a), ([b, next]) => Effect.as(options.set(next), b) )), ) /** * Creates a `Lens` that directly proxies a `SubscriptionRef`. */ export const fromSubscriptionRef = ( ref: SubscriptionRef.SubscriptionRef ): Lens => make({ get get() { return ref.get }, get changes() { return ref.changes }, modify: ( f: (a: A) => Effect.Effect ) => ref.modifyEffect(f), }) export const unwrap = ( effect: Effect.Effect, E1, R1> ): Lens => make({ get: Effect.flatMap(effect, l => l.get), changes: Stream.unwrap(Effect.map(effect, l => l.changes)), modify: ( f: (a: A) => Effect.Effect ) => Effect.flatMap(effect, l => l.modify(f)), }) export const map: { ( self: Lens, get: (a: NoInfer) => B, set: (a: NoInfer, b: B) => NoInfer, ): Lens ( get: (a: NoInfer) => B, set: (a: NoInfer, b: B) => NoInfer, ): (self: Lens) => Lens } = Function.dual(3, ( self: Lens, get: (a: NoInfer) => B, set: (a: NoInfer, b: B) => NoInfer, ): Lens => make({ get get() { return Effect.map(self.get, get) }, get changes() { return Stream.map(self.changes, get) }, modify: ( f: (b: B) => Effect.Effect ) => self.modify(a => Effect.flatMap(f(get(a)), ([c, next]) => Effect.succeed([c, set(a, next)])) ), })) export const mapEffect: { ( self: Lens, get: (a: NoInfer) => Effect.Effect, set: (a: NoInfer, b: B) => Effect.Effect, ESet, RSet>, ): Lens ( get: (a: NoInfer) => Effect.Effect, set: (a: NoInfer, b: B) => Effect.Effect, ESet, RSet>, ): (self: Lens) => Lens } = Function.dual(3, ( self: Lens, get: (a: NoInfer) => Effect.Effect, set: (a: NoInfer, b: B) => Effect.Effect, ESet, RSet>, ): Lens => make({ get get() { return Effect.flatMap(self.get, get) }, get changes() { return Stream.mapEffect(self.changes, get) }, modify: ( f: (b: B) => Effect.Effect ) => 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), ), ) )), })) export const mapStream: { ( self: Lens, f: (changes: Stream.Stream, NoInfer, NoInfer>) => Stream.Stream, NoInfer, NoInfer>, ): Lens ( f: (changes: Stream.Stream, NoInfer, NoInfer>) => Stream.Stream, NoInfer, NoInfer>, ): (self: Lens) => Lens } = Function.dual(2, ( self: Lens, f: (changes: Stream.Stream, NoInfer, NoInfer>) => Stream.Stream, NoInfer, NoInfer>, ): Lens => make({ get get() { return self.get }, get changes() { return f(self.changes) }, modify: self.modify, })) export const mapField: { ( self: Lens, key: K, ): Lens ( key: K, ): (self: Lens) => Lens } = Function.dual(2, ( self: Lens, key: K, ): Lens => map( self, a => a[key], (a, b) => Object.setPrototypeOf({ ...a, [key]: b }, Object.getPrototypeOf(a)), )) export declare namespace mapMutableField { export type WritableKeys = { [K in keyof T]-?: IfEquals< { [P in K]: T[K] }, { -readonly [P in K]: T[K] }, K, never > }[keyof T] type IfEquals = (() => T extends X ? 1 : 2) extends (() => T extends Y ? 1 : 2) ? A : B } export const mapMutableField: { , ER, EW, RR, RW>( self: Lens, key: K, ): Lens , ER, EW, RR, RW>( key: K, ): (self: Lens) => Lens } = Function.dual(2, , ER, EW, RR, RW>( self: Lens, key: K, ): Lens => map(self, a => a[key], (a, b) => { a[key] = b; return a })) export const mapArrayAt: { ( self: Lens, index: number, ): Lens ( index: number ): (self: Lens) => Lens } = Function.dual(2, ( self: Lens, index: number, ): Lens => mapEffect( self, Array.get(index), (a, b) => Array.replaceOption(a, index, b) as any, )) export const mapMutableArrayAt: { ( self: Lens, index: number, ): Lens ( index: number ): (self: Lens) => Lens } = Function.dual(2, ( self: Lens, index: number, ): Lens => mapEffect( self, Array.get(index), (a, b) => Effect.flatMap( Array.get(a, index), () => Effect.as(Effect.sync(() => { a[index] = b }), a), ), )) export const mapChunkAt: { ( self: Lens, ER, EW, RR, RW>, index: number, ): Lens ( index: number ): (self: Lens, ER, EW, RR, RW>) => Lens } = Function.dual(2, ( self: Lens, ER, EW, RR, RW>, index: number, ): Lens => mapEffect( self, Chunk.get(index), (a, b) => Effect.succeed(Chunk.replace(a, index, b))), ) export const get = (self: Lens): Effect.Effect => self.get export const set: { (value: A): (self: Lens) => Effect.Effect (self: Lens, value: A): Effect.Effect } = Function.dual(2, (self: Lens, value: A) => self.modify(() => Effect.succeed([void 0, value] as const)), ) export const getAndSet: { (value: A): (self: Lens) => Effect.Effect (self: Lens, value: A): Effect.Effect } = Function.dual(2, (self: Lens, value: A) => self.modify(a => Effect.succeed([a, value] as const)), ) export const update: { (f: (a: A) => A): (self: Lens) => Effect.Effect (self: Lens, f: (a: A) => A): Effect.Effect } = Function.dual(2, (self: Lens, f: (a: A) => A) => self.modify(a => Effect.succeed([void 0, f(a)] as const)), ) export const updateEffect: { (f: (a: A) => Effect.Effect): (self: Lens) => Effect.Effect (self: Lens, f: (a: A) => Effect.Effect): Effect.Effect } = Function.dual(2, (self: Lens, f: (a: A) => Effect.Effect) => self.modify(a => Effect.flatMap( f(a), next => Effect.succeed([void 0, next] as const), )), ) export const getAndUpdate: { (f: (a: A) => A): (self: Lens) => Effect.Effect (self: Lens, f: (a: A) => A): Effect.Effect } = Function.dual(2, (self: Lens, f: (a: A) => A) => self.modify(a => Effect.succeed([a, f(a)] as const)), ) export const getAndUpdateEffect: { (f: (a: A) => Effect.Effect): (self: Lens) => Effect.Effect (self: Lens, f: (a: A) => Effect.Effect): Effect.Effect } = Function.dual(2, (self: Lens, f: (a: A) => Effect.Effect) => self.modify(a => Effect.flatMap( f(a), next => Effect.succeed([a, next] as const) )), ) export const setAndGet: { (value: A): (self: Lens) => Effect.Effect (self: Lens, value: A): Effect.Effect } = Function.dual(2, (self: Lens, value: A) => self.modify(() => Effect.succeed([value, value] as const)), ) export const updateAndGet: { (f: (a: A) => A): (self: Lens) => Effect.Effect (self: Lens, f: (a: A) => A): Effect.Effect } = Function.dual(2, (self: Lens, f: (a: A) => A) => self.modify(a => { const next = f(a) return Effect.succeed([next, next] as const) }), ) export const updateAndGetEffect: { (f: (a: A) => Effect.Effect): (self: Lens) => Effect.Effect (self: Lens, f: (a: A) => Effect.Effect): Effect.Effect } = Function.dual(2, (self: Lens, f: (a: A) => Effect.Effect) => self.modify(a => Effect.flatMap( f(a), next => Effect.succeed([next, next] as const), )), )