Optic
Reads and updates focused parts of values without mutating the original value.
An optic describes where to look inside a value, such as a record field, a union variant, an optional value, or several values in a collection. Different optic types describe different kinds of focus: some always find a value, some may not, and some can find many. This module includes the optic types, constructors, focusing helpers, and operations for replacing, modifying, or collecting focused values.
Constructors
Iso that converts a Record<string, A> to an array of
[key, value] entries and back.
When to use
Use when you want to traverse or manipulate record entries as an array (e.g.
with .forEach()).
Details
getusesObject.entries.setusesObject.fromEntries.- Round-trip is lossless for
Record<string, A>.
See
Signature
declare function entries<A>(): Iso<Record<string, A>, readonly Array<readonly [string, A]>>Example
(Traversing record values)
import { Optic, Schema } from "effect"
const _positiveValues = Optic.entries<number>() .forEach((entry) => entry.key(1).check(Schema.isGreaterThan(0)))
const inc = _positiveValues.modifyAll((n) => n + 1)
inc({ a: 0, b: 3, c: -1 }) // => { a: 0, b: 4, c: -1 }Prism that focuses on the failure value of a Result.
When to use
Use when you have a Result<A, E> and want to read/update E only when it
is a Failure.
Details
getResultfails when the result is aSuccess.set(e)producesResult.fail(e).
See
Signature
declare function failure<A, E>(): Prism<Result<A, E>, E>Example
(Accessing failure)
import { Optic, Result } from "effect"
const _err = Optic.id<Result.Result<number, string>>().compose(Optic.failure())
_err.getResult(Result.fail("oops")) // => Result.succeed("oops")
Result.isFailure(_err.getResult(Result.succeed(42))) // => truefromChecks
Creates a Prism from one or more Schema validation checks.
When to use
Use when you want to narrow T to the subset that passes certain validation
rules (e.g. positive integer).
- You already have
Schema.isGreaterThan,Schema.isInt, etc.
Details
getResultruns all checks and preserves their structured issues when any check fails.setis identity — the value passes through unchanged.
See
Signature
declare function fromChecks<T>(...checks: readonly [Check<T>, Check<T>]): Prism<T, T>Example
(Creating a positive integer prism)
import { Optic, Result, Schema } from "effect"
const posInt = Optic.fromChecks<number>( Schema.isGreaterThan(0), Schema.isInt())
posInt.getResult(3) // => Result.succeed(3)
Result.isFailure(posInt.getResult(-1)) // => trueIso that focuses on the whole value unchanged.
When to use
Use when you need to start an optic chain with a focus on the whole value.
Details
get(s)returnss.set(a)returnsa.- Singleton — every call returns the same instance.
See
- Iso — the type this function returns
Signature
declare function id<S>(): Iso<S, S>Example
(Starting an optic chain)
import { Optic } from "effect"
type S = { readonly x: number }
const _x = Optic.id<S>().key("x")
_x.get({ x: 42 }) // => 42Creates an Iso from a pair of conversion functions.
When to use
Use when you have two pure conversion functions that preserve all information
between S and A.
Details
The returned optic can be composed with any other optic.
See
Signature
declare function makeIso<S, A>(get: (s: S) => A, set: (a: A) => S): Iso<S, A>Example
(Wrapping and unwrapping a branded type)
import { Optic } from "effect"
type Meters = { readonly value: number }const meters = Optic.makeIso<Meters, number>( (m) => m.value, (n) => ({ value: n }))
meters.get({ value: 100 }) // => 100
meters.set(42) // => { value: 42 }Creates a Lens from a getter and a replacer.
When to use
Use when you can always extract A from S and produce a new S by
substituting a new A.
Details
replace(a, s)should return a structurally newSwithain place of the old focus.
See
Signature
declare function makeLens<S, A>(get: (s: S) => A, replace: (a: A, s: S) => S): Lens<S, A>Example
(Focusing on the first element of a pair)
import { Optic } from "effect"
const _first = Optic.makeLens<readonly [string, number], string>( (pair) => pair[0], (s, pair) => [s, pair[1]])
_first.get(["hello", 42]) // => "hello"
_first.replace("world", ["hello", 42]) // => ["world", 42]makeOptional
Creates an Optional from a fallible getter and a fallible setter.
When to use
Use when you need an optic for a focus that may be missing on read and may reject updates on write.
Details
getResultshould returnResult.fail(issue)on mismatch.setshould returnResult.fail(issue)when the update cannot be applied.- Issues are not formatted automatically; callers choose how to render them.
See
Signature
declare function makeOptional<S, A>(getResult: (s: S) => Result<A, Issue>, set: (a: A, s: S) => Result<S, Issue>): Optional<S, A>Example
(Accessing record keys safely)
import { Optic, Result, SchemaIssue } from "effect"
const atKey = (key: string) => { const issue = new SchemaIssue.Pointer([key], new SchemaIssue.MissingKey(undefined)) return Optic.makeOptional<Record<string, number>, number>( (s) => Object.hasOwn(s, key) ? Result.succeed(s[key]) : Result.fail(issue), (a, s) => Object.hasOwn(s, key) ? Result.succeed({ ...s, [key]: a }) : Result.fail(issue) )}
atKey("x").getResult({ x: 1 }) // => Result.succeed(1)Creates a Prism from a fallible getter and an infallible setter.
When to use
Use when reading can fail (the part may not exist in S), but building S
from A always succeeds.
Details
getResultshould returnResult.fail(issue)on mismatch.- Issues are not formatted automatically; callers choose how to render them.
See
- Prism — the type this function returns
- fromChecks — build from
Schemachecks instead
Signature
declare function makePrism<S, A>(getResult: (s: S) => Result<A, Issue>, set: (a: A) => S): Prism<S, A>Example
(Parsing a string to a number)
import { Optic, Result, SchemaIssue } from "effect"
const numeric = Optic.makePrism<string, number>( (s) => { const n = Number(s) return Number.isNaN(n) ? Result.fail(new SchemaIssue.InvalidValue({ message: "not a number" })) : Result.succeed(n) }, String)
numeric.getResult("42") // => Result.succeed(42)
numeric.set(42) // => "42"Prism that focuses on Option.None, exposing undefined.
When to use
Use when you want to match or construct None values within an optic chain.
Details
getResultsucceeds withundefinedwhen the option isNone.getResultfails when the option isSome.set(undefined)producesOption.none().
See
Signature
declare function none<A>(): Prism<Option<A>, undefined>Example
(Matching None)
import { Optic, Option, Result } from "effect"
const _none = Optic.id<Option.Option<number>>().compose(Optic.none())
_none.getResult(Option.none()) // => Result.succeed(undefined)
Result.isFailure(_none.getResult(Option.some(1))) // => truePrism that focuses on the value inside Option.Some.
When to use
Use when you have an Option<A> and want to read/update the inner value only
when it is Some.
Details
getResultfails with a structured issue when the option isNone.set(a)wrapsainOption.some(a).
See
Signature
declare function some<A>(): Prism<Option<A>, A>Example
(Accessing Some value)
import { Optic, Option, Result } from "effect"
const _some = Optic.id<Option.Option<number>>().compose(Optic.some())
_some.getResult(Option.some(42)) // => Result.succeed(42)
Result.isFailure(_some.getResult(Option.none())) // => true
_some.set(10) // => Option.some(10)Prism that focuses on the success value of a Result.
When to use
Use when you have a Result<A, E> and want to read/update A only when it
is a Success.
Details
getResultfails when the result is aFailure.set(a)producesResult.succeed(a).
See
Signature
declare function success<A, E>(): Prism<Result<A, E>, A>Example
(Accessing success)
import { Optic, Result } from "effect"
const _ok = Optic.id<Result.Result<number, string>>().compose(Optic.success())
_ok.getResult(Result.succeed(42)) // => Result.succeed(42)
Result.isFailure(_ok.getResult(Result.fail("err"))) // => trueGetters
Reads the focused value from a Lens.
When to use
Use when the optic always focuses exactly one value.
Details
Supports both data-first and data-last forms.
See
- getResult for optics whose focus may be absent
Signature
declare const get: { <S, A>(optic: Lens<S, A>): (self: NoInfer<S>) => A; <S, A>(self: NoInfer<S>, optic: Lens<S, A>): A;}Extracts all values focused by a Traversal as a plain mutable array.
When to use
Use when you need the focused values as a simple Array<A> for further
processing.
Details
- Returns an empty array when the traversal cannot focus.
- Always returns a fresh array (safe to mutate).
- Supports both data-first and data-last forms.
See
- Traversal — the optic type this operates on
Signature
declare const getAll: { <S, A>(traversal: Traversal<S, A>): (self: NoInfer<S>) => Array<A>; <S, A>(self: NoInfer<S>, traversal: Traversal<S, A>): Array<A>;}Example
(Collecting positive numbers)
import { Optic, Schema } from "effect"
type S = { readonly values: ReadonlyArray<number> }
const _pos = Optic.id<S>() .key("values") .forEach((n) => n.check(Schema.isGreaterThan(0)))
const getPositive = Optic.getAll(_pos)
getPositive({ values: [3, -1, 5] }) // => [3, 5]
getPositive({ values: [-1, -2] }) // => []Attempts to read the focused value from an Optional.
When to use
Use when the optic may not focus and you need the failure as a Result.
Details
Supports both data-first and data-last forms.
See
- get for optics that always focus
Signature
declare const getResult: { <S, A>(optic: Optional<S, A>): (self: NoInfer<S>) => Result<A, Issue>; <S, A>(self: NoInfer<S>, optic: Optional<S, A>): Result<A, Issue>;}Models
A lossless, reversible conversion between types S and A.
When to use
Use when you have a pair of functions that convert back and forth without losing
information (e.g. Record ↔ entries, Celsius ↔ Fahrenheit).
- You want the strongest optic that can be composed with any other.
Details
get(s)always succeeds and returns anA.set(a)always succeeds and returns anS.get(set(a)) === aandset(get(s))equalss(round-trip laws).- Extends both Lens and Prism.
See
Signature
interface Iso<in out S, in out A> extends Lens<S, A>, Prism<S, A> {}Example
(Converting between Celsius and Fahrenheit)
import { Optic } from "effect"
const fahrenheit = Optic.makeIso<number, number>( (c) => c * 9 / 5 + 32, (f) => (f - 32) * 5 / 9)
fahrenheit.get(100) // => 212
fahrenheit.set(32) // => 0Focuses on exactly one part A inside a whole S.
When to use
Use when you always have a value to read and need the original S to produce
the updated whole, unlike Iso.
Details
get(s)always succeeds and returnsA.replace(a, s)returns a newSwith the focused part replaced.- Extends Optional.
- Composing a Lens with a Prism or Optional produces an Optional.
See
Signature
interface Lens<in out S, in out A> extends Optional<S, A> { readonly get: (s: S) => A;}Example
(Focusing on a struct field)
import { Optic } from "effect"
type Person = { readonly name: string; readonly age: number }
const _name = Optic.id<Person>().key("name")
_name.get({ name: "Alice", age: 30 }) // => "Alice"The most general optic — both reading and writing can fail.
When to use
Use when the focus may not exist in S and writing a new A back may also
fail, for example when the source no longer matches the expected shape. This
is the base type extended by Iso, Lens, Prism, and
Traversal.
Details
getResult(s)returnsResult.Success<A>orResult.Failure<SchemaIssue.Issue>.replaceResult(a, s)returnsResult.Success<S>orResult.Failure<SchemaIssue.Issue>.replace(a, s)returns the originalson failure (never throws).modify(f)returns the originalson failure (never throws).- All operations are pure; inputs are never mutated.
See
- makeOptional — constructor
- Lens — when reading always succeeds
- Prism — when writing always succeeds
Signature
interface Optional<in out S, in out A> { readonly getResult: (s: S) => Result<A, Issue>; readonly replace: (a: A, s: S) => S; readonly replaceResult: (a: A, s: S) => Result<S, Issue>; at<S, A extends object, Key extends string | number | symbol>(this: Optional<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `at` on a union type">): Optional<S, A[Key]>; check<S, A>(this: Prism<S, A>, ...checks: readonly [Check<A>, Check<A>]): Prism<S, A>; check<S, A>(this: Optional<S, A>, ...checks: readonly [Check<A>, Check<A>]): Optional<S, A>; compose<B>(this: Iso<S, A>, that: Iso<A, B>): Iso<S, B>; compose<B>(this: Lens<S, A>, that: Lens<A, B>): Lens<S, B>; compose<B>(this: Prism<S, A>, that: Prism<A, B>): Prism<S, B>; compose<B>(this: Optional<S, A>, that: Optional<A, B>): Optional<S, B>; forEach<S, A, B>(this: Traversal<S, A>, f: (iso: Iso<A, A>) => Optional<A, B>): Traversal<S, B>; key<S, A extends object, Key extends string | number | symbol>(this: Lens<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `key` on a union type">): Lens<S, A[Key]>; key<S, A extends object, Key extends string | number | symbol>(this: Optional<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `key` on a union type">): Optional<S, A[Key]>; modify(f: (a: A) => A): (s: S) => S; modifyAll<S, A>(this: Traversal<S, A>, f: (a: A) => A): (s: S) => S; notUndefined<S, A>(this: Prism<S, A>): Prism<S, Exclude<A, undefined>>; notUndefined<S, A>(this: Optional<S, A>): Optional<S, Exclude<A, undefined>>; omit<S, A, Keys extends readonly Array<keyof A>>(this: Lens<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `omit` on a union type">): Lens<S, Omit<A, Keys[number]>>; omit<S, A, Keys extends readonly Array<keyof A>>(this: Optional<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `omit` on a union type">): Optional<S, Omit<A, Keys[number]>>; optionalKey<S, A extends object, Key extends string | number | symbol>(this: Lens<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `optionalKey` on a union type">): Lens<S, A[Key] | undefined>; optionalKey<S, A extends object, Key extends string | number | symbol>(this: Optional<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `optionalKey` on a union type">): Optional<S, A[Key] | undefined>; pick<S, A, Keys extends readonly Array<keyof A>>(this: Lens<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `pick` on a union type">): Lens<S, Pick<A, Keys[number]>>; pick<S, A, Keys extends readonly Array<keyof A>>(this: Optional<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `pick` on a union type">): Optional<S, Pick<A, Keys[number]>>; refine<S, A, B>(this: Prism<S, A>, refinement: (a: A) => a is B, annotations?: Filter): Prism<S, B>; refine<S, A, B>(this: Optional<S, A>, refinement: (a: A) => a is B, annotations?: Filter): Optional<S, B>; tag<S, A extends { readonly _tag: LiteralValue; }, Tag extends LiteralValue>(this: Prism<S, A>, tag: Tag): Prism<S, Extract<A, { readonly _tag: Tag; }>>; tag<S, A extends { readonly _tag: LiteralValue; }, Tag extends LiteralValue>(this: Optional<S, A>, tag: Tag): Optional<S, Extract<A, { readonly _tag: Tag; }>>;}Example
(Focusing on an optional record key)
import { Optic, Result } from "effect"
type Env = { [key: string]: string }const _home = Optic.id<Env>().at("HOME")
_home.getResult({ HOME: "/root" }) // => Result.succeed("/root")
Result.isFailure(_home.getResult({ PATH: "/bin" })) // => true
// replace returns original on failure_home.replace("/new", { PATH: "/bin" }) // => { PATH: "/bin" }Focuses on a part A of S that may not be present (e.g. a union
variant or a validated subset).
When to use
Use when the focus is conditional — reading can fail (wrong variant, failed validation).
- Building a new
SfromAdoes not require the originalS.
Details
getResult(s)returnsResult.Success<A>when the focus matches, orResult.Failure<SchemaIssue.Issue>with a structured issue.set(a)always succeeds and returns a newS.- Extends Optional.
- Composing two Prisms produces a Prism; composing a Prism with a Lens produces an Optional.
See
- makePrism — constructor
- fromChecks — build a Prism from schema checks
- Lens — when reading always succeeds
Signature
interface Prism<in out S, in out A> extends Optional<S, A> { readonly set: (a: A) => S;}Example
(Narrowing a tagged union)
import { Optic, Result } from "effect"
type Shape = | { readonly _tag: "Circle"; readonly radius: number } | { readonly _tag: "Rect"; readonly width: number }
const _circle = Optic.id<Shape>().tag("Circle")
_circle.getResult({ _tag: "Circle", radius: 5 }) // => Result.succeed({ _tag: "Circle", radius: 5 })
Result.isFailure(_circle.getResult({ _tag: "Rect", width: 10 })) // => trueAn optic that focuses on zero or more elements of type A inside S.
When to use
Use when you want to read/update multiple elements at once (e.g. all items in an array, or a filtered subset).
Details
- Technically
Optional<S, ReadonlyArray<A>>— the focused value is an array of all matched elements. - Use
.forEach()to add per-element sub-optics (filtering, drilling deeper). - Use
.modifyAll(f)to map a function over every focused element. - Use getAll to extract all focused elements as a plain array.
See
Signature
interface Traversal<in out S, in out A> extends Optional<S, ReadonlyArray<A>> {}Example
(Traversing array elements with a filter)
import { Optic, Schema } from "effect"
type S = { readonly items: ReadonlyArray<number> }
const _positive = Optic.id<S>() .key("items") .forEach((n) => n.check(Schema.isGreaterThan(0)))
const getPositive = Optic.getAll(_positive)
getPositive({ items: [1, -2, 3] }) // => [1, 3]Transforming
Transforms the focused value in a source.
When to use
Use when you want to update a focus with a function.
Details
Supports both data-first and data-last forms. A failed focus leaves the source unchanged.
See
- modifyAll for transforming every value in a traversal
Signature
declare const modify: { <S, A>(optic: Optional<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): (self: NoInfer<S>) => S; <S, A>(self: NoInfer<S>, optic: Optional<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): S;}Transforms every value focused by a Traversal.
When to use
Use when you want to update each value selected by a traversal.
Details
Supports both data-first and data-last forms. A failed traversal leaves the source unchanged.
See
Signature
declare const modifyAll: { <S, A>(traversal: Traversal<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): (self: NoInfer<S>) => S; <S, A>(self: NoInfer<S>, traversal: Traversal<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): S;}Replaces the focused value in a source.
When to use
Use when a failed focus should leave the source unchanged.
Details
Supports both data-first and data-last forms.
See
- replaceResult for an explicit replacement failure
Signature
declare const replace: { <S, A>(optic: Optional<S, A>, value: NoInfer<A>): (self: NoInfer<S>) => S; <S, A>(self: NoInfer<S>, optic: Optional<S, A>, value: NoInfer<A>): S;}replaceResult
Attempts to replace the focused value in a source.
When to use
Use when you need an explicit Result for a replacement failure.
Details
Supports both data-first and data-last forms.
See
- replace for returning the original source on failure
Signature
declare const replaceResult: { <S, A>(optic: Optional<S, A>, value: NoInfer<A>): (self: NoInfer<S>) => Result<S, Issue>; <S, A>(self: NoInfer<S>, optic: Optional<S, A>, value: NoInfer<A>): Result<S, Issue>;}Builds a source value from a focused value using a Prism.
When to use
Use when the optic can construct the source without an existing source value.
Details
Supports both data-first and data-last forms. The focused value is self.
See
- replace for updates that use an existing source
Signature
declare const set: { <S, A>(optic: Prism<S, A>): (self: NoInfer<A>) => S; <S, A>(self: NoInfer<A>, optic: Prism<S, A>): S;}