From dd3207aefb081c1b337b8bf5c2acbd657f89e416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 25 May 2026 05:30:18 +0200 Subject: [PATCH] Refactor --- packages/effect-lens/src/Lens.ts | 264 +++++++++++++++++-------------- 1 file changed, 145 insertions(+), 119 deletions(-) diff --git a/packages/effect-lens/src/Lens.ts b/packages/effect-lens/src/Lens.ts index 1f52957..ecb2adf 100644 --- a/packages/effect-lens/src/Lens.ts +++ b/packages/effect-lens/src/Lens.ts @@ -61,6 +61,16 @@ export const isLensStep = (u: unknown): u is LensStep => Predicate.hasProperty(u, LensImplTypeId) + +export const asLensImpl = ( + lens: Lens +): LensImpl => { + if (!isLensImpl(lens)) + throw new Error("Not a 'LensImpl'.") + return lens as LensImpl +} + export abstract class LensImpl< in out A, in out B, @@ -86,7 +96,7 @@ extends Pipeable.Class() implements Lens { abstract sourceCommit(b: B): Effect.Effect abstract readonly withLock: (self: Effect.Effect) => Effect.Effect - private get access(): Effect.Effect, ER, RR> { + get access(): Effect.Effect, ER, RR> { let effect: Effect.Effect, unknown, unknown> = Effect.map( this.sourceGet, value => ({ @@ -160,19 +170,12 @@ extends LensImpl { get withLock() { return this.source.withLock } } -/** - * Creates a `Lens` by supplying how to read the current value, observe changes, and apply transformations. - */ -// export const make = ( -// source: LensImpl.Source -// ): Lens => new LensImpl(source) - /** * Creates a `Lens` by supplying how to read the current value, observe changes, and apply transformations. */ export const makeLazy = ( source: LensLazyImpl.Source -): Lens => new LensLazyImpl(source) +): Lens => new LensLazyImpl(source) /** * Derives a new `Lens` by immutably appending a step to an existing `LensImpl`. @@ -285,13 +288,14 @@ export const fromSubscriptionRef = ( */ 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)), - update: a => Effect.flatMap(effect, l => asLensWithInternals(l).update(a)), - withLock: ( - effect2: Effect.Effect - ) => Effect.flatMap(effect, l => asLensWithInternals(l).withLock(effect2)), +): Lens => makeLazy({ + sourceGet: Effect.flatMap(effect, l => l.get), + sourceChanges: Stream.unwrap(Effect.map(effect, l => l.changes)), + sourceCommit: a => Effect.flatMap( + effect, + l => Effect.flatMap(asLensImpl(l).access, frame => frame.commit(Effect.succeed(a))), + ), + withLock: identity, }) /** @@ -311,12 +315,11 @@ export const map: { self: Lens, get: (a: NoInfer) => B, update: (a: NoInfer, b: B) => NoInfer, -): Lens => makeLazy({ - get get() { return Effect.map(self.get, get) }, - get changes() { return Stream.map(self.changes, get) }, - update(a: A) { return }, - get withLock() { return asLensWithInternals(self).withLock }, -})) +): Lens => mapEffect( + self, + a => Effect.succeed(get(a)), + (a, b) => Effect.succeed(update(a, b)), +)) /** * Derives a new `Lens` by applying effectful getters and setters over the focused value. @@ -335,22 +338,25 @@ export const mapEffect: { 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), +): Lens => { + return derive( + asLensImpl(self), + { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.flatMap( + parent, + frame => Effect.map( + get(frame.value), + value => ({ + value, + commit: next => frame.commit(Effect.flatMap(next, b => set(frame.value, b))), + }), + ), ), - ) - )), -})) + transformStream: stream => Stream.mapEffect(stream, get), + }, + ) +}) /** * Derives a new `Lens` by applying synchronous getters and setters over the value inside an `Option`. @@ -432,11 +438,11 @@ export const mapStream: { } = 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) }, - get modify() { return self.modify }, -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => parent, + transformStream: f, +} as LensStep)) /** @@ -455,11 +461,11 @@ export const mapErrorRead: { } = Function.dual(2, ( self: Lens, f: (error: NoInfer) => E2, -): Lens => make({ - get get() { return Effect.mapError(self.get, f) }, - get changes() { return Stream.mapError(self.changes, f) }, - get modify() { return self.modify as any }, -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.mapError(parent, f), + transformStream: stream => Stream.mapError(stream, f), +} as LensStep)) /** * Transforms modify errors of a `Lens`. @@ -478,13 +484,14 @@ export const mapErrorWrite: { } = Function.dual(2, ( self: Lens, f: (error: unknown) => E2, -): Lens => make({ - get get() { return self.get }, - get changes() { return self.changes }, - modify: ( - g: (a: A) => Effect.Effect - ) => Effect.mapError(self.modify(g), f), -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.map(parent, frame => ({ + value: frame.value, + commit: next => Effect.mapError(frame.commit(next), f), + })), + transformStream: stream => stream, +} as LensStep)) /** * Transforms all errors of a `Lens`. @@ -503,13 +510,17 @@ export const mapError: { } = Function.dual(2, ( self: Lens, f: (error: unknown) => E2, -): Lens => make({ - get get() { return Effect.mapError(self.get, f) }, - get changes() { return Stream.mapError(self.changes, f) }, - modify: ( - g: (a: A) => Effect.Effect - ) => Effect.mapError(self.modify(g), f), -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.map( + Effect.mapError(parent, f), + frame => ({ + value: frame.value, + commit: next => Effect.mapError(frame.commit(next), f), + }), + ), + transformStream: stream => Stream.mapError(stream, f), +} as LensStep)) /** * Recovers from read failures of a `Lens`. @@ -527,11 +538,11 @@ export const catchAllRead: { } = Function.dual(2, ( self: Lens, f: (error: NoInfer) => Subscribable.Subscribable, -): Lens => 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 }, -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.catchAll(parent, error => asLensImpl(f(error) as Lens).access), + transformStream: stream => Stream.catchAll(stream, error => f(error).changes), +} as LensStep)) /** * Runs an effect when read failures occur. @@ -549,11 +560,11 @@ export const tapErrorRead: { } = Function.dual(2, ( self: Lens, f: (error: NoInfer) => Effect.Effect, -): Lens => make({ - get get() { return Effect.tapError(self.get, f) }, - get changes() { return Stream.tapError(self.changes, f) }, - get modify() { return self.modify as any }, -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.tapError(parent, f), + transformStream: stream => Stream.tapError(stream, f), +} as LensStep)) /** * Runs an effect when modify failures occur. @@ -572,13 +583,14 @@ export const tapErrorWrite: { } = Function.dual(2, ( self: Lens, f: (error: unknown) => Effect.Effect, -): Lens => make({ - get get() { return self.get }, - get changes() { return self.changes }, - modify: ( - g: (a: A) => Effect.Effect - ) => Effect.tapError(self.modify(g), f), -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.map(parent, frame => ({ + value: frame.value, + commit: next => Effect.tapError(frame.commit(next), f), + })), + transformStream: stream => stream, +} as LensStep)) /** * Runs an effect when any `Lens` failure occurs. @@ -597,13 +609,17 @@ export const tapError: { } = Function.dual(2, ( self: Lens, f: (error: unknown) => Effect.Effect, -): Lens => make({ - get get() { return Effect.tapError(self.get, f) }, - get changes() { return Stream.tapError(self.changes, f) }, - modify: ( - g: (a: A) => Effect.Effect - ) => Effect.tapError(self.modify(g), f), -})) +): Lens => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.map( + Effect.tapError(parent, f), + frame => ({ + value: frame.value, + commit: next => Effect.tapError(frame.commit(next), f), + }), + ), + transformStream: stream => Stream.tapError(stream, f), +} as LensStep)) /** @@ -620,13 +636,17 @@ export const provideContext: { } = Function.dual(2, ( self: Lens, context: Context.Context, -): Lens, Exclude> => make({ - get get() { return Effect.provide(self.get, context) }, - get changes() { return Stream.provideSomeContext(self.changes, context) }, - modify: ( - f: (a: A) => Effect.Effect - ) => Effect.provide(self.modify(f), context), -})) +): Lens, Exclude> => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.map( + Effect.provide(parent, context), + frame => ({ + value: frame.value, + commit: next => Effect.provide(frame.commit(Effect.provide(next, context)), context), + }), + ), + transformStream: stream => Stream.provideSomeContext(stream, context), +} as LensStep, RR, Exclude, RW>)) /** * Provides a `Runtime` or `ManagedRuntime` to a `Lens`, removing it from both the read and write environments. @@ -651,18 +671,20 @@ export const provideRuntime: { } = Function.dual(2, ( self: Lens, runtime: Runtime.Runtime, -) => make, Exclude>({ - get get() { return Effect.provide(self.get, runtime) }, - get changes() { - return Stream.unwrap(Effect.map( +) => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.map( + Effect.provide(parent, runtime), + frame => ({ + value: frame.value, + commit: next => Effect.provide(frame.commit(Effect.provide(next, runtime)), runtime), + }), + ), + transformStream: stream => Stream.unwrap(Effect.map( Effect.provide(Effect.context(), runtime), - context => Stream.provideContext(self.changes, context), - )) - }, - modify: ( - f: (a: A) => Effect.Effect - ) => Effect.provide(self.modify(f), runtime), -})) + context => Stream.provideContext(stream, context), + )), +} as LensStep, RR, Exclude, RW>)) /** * Provides a single service to a `Lens`, removing it from both the read and write environments. @@ -684,13 +706,17 @@ export const provideService: { self: Lens, tag: Context.Tag, service: NoInfer, -): Lens, Exclude> => make({ - get get() { return Effect.provideService(self.get, tag, service) }, - get changes() { return Stream.provideService(self.changes, tag, service) }, - modify: ( - f: (a: A) => Effect.Effect - ) => Effect.provideService(self.modify(f), tag, service), -})) +): Lens, Exclude> => derive(asLensImpl(self), { + [LensStepTypeId]: LensStepTypeId, + access: parent => Effect.map( + Effect.provideService(parent, tag, service), + frame => ({ + value: frame.value, + commit: next => Effect.provideService(frame.commit(Effect.provideService(next, tag, service)), tag, service), + }), + ), + transformStream: stream => Stream.provideService(stream, tag, service), +} as LensStep, RR, Exclude, RW>)) /** @@ -876,7 +902,7 @@ 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)), + self.modifyEffect(() => Effect.succeed([void 0, value] as const)), ) /** @@ -886,7 +912,7 @@ 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)), + self.modifyEffect(a => Effect.succeed([a, value] as const)), ) /** @@ -896,7 +922,7 @@ 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)), + self.modifyEffect(a => Effect.succeed([void 0, f(a)] as const)), ) /** @@ -906,7 +932,7 @@ 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( + self.modifyEffect(a => Effect.flatMap( f(a), next => Effect.succeed([void 0, next] as const), )), @@ -919,7 +945,7 @@ 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)), + self.modifyEffect(a => Effect.succeed([a, f(a)] as const)), ) /** @@ -929,7 +955,7 @@ 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( + self.modifyEffect(a => Effect.flatMap( f(a), next => Effect.succeed([a, next] as const) )), @@ -942,7 +968,7 @@ 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)), + self.modifyEffect(() => Effect.succeed([value, value] as const)), ) /** @@ -952,7 +978,7 @@ 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 => { + self.modifyEffect(a => { const next = f(a) return Effect.succeed([next, next] as const) }), @@ -965,7 +991,7 @@ 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( + self.modifyEffect(a => Effect.flatMap( f(a), next => Effect.succeed([next, next] as const), )),