diff --git a/packages/effect-lens/src/Lens.ts b/packages/effect-lens/src/Lens.ts index 96284fe..f265eee 100644 --- a/packages/effect-lens/src/Lens.ts +++ b/packages/effect-lens/src/Lens.ts @@ -213,6 +213,42 @@ export const mapOption: { }), )) +/** + * 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: { + ( + self: Lens, ER, EW, RR, RW>, + get: (a: NoInfer) => Effect.Effect, + set: (a: NoInfer, b: B) => Effect.Effect, ESet, RSet>, + ): Lens, ER | EGet, EW | ESet, RR | RGet, RW | RSet> + ( + get: (a: NoInfer) => Effect.Effect, + set: (a: NoInfer, b: B) => Effect.Effect, ESet, RSet>, + ): (self: Lens, ER, EW, RR, RW>) => Lens, ER | EGet, EW | ESet, RR | RGet, RW | RSet> +} = Function.dual(3, ( + self: Lens, ER, EW, RR, RW>, + get: (a: NoInfer) => Effect.Effect, + set: (a: NoInfer, b: B) => Effect.Effect, ESet, RSet>, +): Lens, 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. */