import { Chunk, Effect, Function, Pipeable, Predicate, Readable, Stream, Subscribable, SubscriptionRef } from "effect" 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: ref.modifyEffect.bind(ref), // TODO }) 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 => 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 => 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 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), )), ) Effect.gen(function*() { const myChunkRef = yield* SubscriptionRef.make(Chunk.make(12, 38, 69) as Chunk.Chunk) const myChunkLens = fromSubscriptionRef(myChunkRef) const chunkValueLens = mapEffect(myChunkLens, Chunk.get(1), (a, b) => Effect.succeed(Chunk.replace(a, 1, b))) console.log(yield* myChunkRef.get, yield* chunkValueLens.get) // yield* update(myChunkLens, Chunk.replace(1, 22)) yield* set(chunkValueLens, 22) console.log(yield* myChunkRef.get, yield* chunkValueLens.get) }).pipe( Effect.runSync )