From 2328e1fc3da03341cc6a3c82461a6d6d8da2137e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 28 Mar 2026 20:51:42 +0100 Subject: [PATCH] Add mapOption --- packages/effect-lens/src/Lens.ts | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/effect-lens/src/Lens.ts b/packages/effect-lens/src/Lens.ts index 679e0dc..96284fe 100644 --- a/packages/effect-lens/src/Lens.ts +++ b/packages/effect-lens/src/Lens.ts @@ -1,4 +1,4 @@ -import { Array, Chunk, Effect, Function, Pipeable, Predicate, Readable, Stream, type SubscriptionRef, type SynchronizedRef } from "effect" +import { Array, Chunk, Effect, Function, Option, Pipeable, Predicate, Readable, Stream, type SubscriptionRef, type SynchronizedRef } from "effect" import type { NoSuchElementException } from "effect/Cause" import * as Subscribable from "./Subscribable.js" @@ -183,6 +183,36 @@ export const mapEffect: { )), })) +/** + * 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: { + ( + self: Lens, ER, EW, RR, RW>, + get: (a: NoInfer) => B, + set: (a: NoInfer, b: B) => NoInfer, + ): Lens, ER, EW, RR, RW> + ( + get: (a: NoInfer) => B, + set: (a: NoInfer, b: B) => NoInfer, + ): (self: Lens, ER, EW, RR, RW>) => Lens, ER, EW, RR, RW> +} = Function.dual(3, ( + self: Lens, ER, EW, RR, RW>, + get: (a: NoInfer) => B, + set: (a: NoInfer, b: B) => NoInfer, +): Lens, 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(), + }), +)) + /** * Allows transforming only the `changes` stream of a `Lens` while keeping the focus type intact. */