Optic

dev.constructive.eo.optics.Optic
See theOptic companion object
trait Optic[S, T, A, B, F[_, _]]

Existential encoding of a profunctor optic — the single trait behind every optic family in cats-eo. The optic is a pair of functions (S => F[X, A], F[X, B] => T) over a carrier F[_, _] and an existential X that threads the leftover information needed to rebuild T. Each family picks a different carrier (Tuple2 for Lens, Either for Prism, Affine for Optional, …). Operations live in the companion as capability-gated extensions; each extension's (using …) clause names the typeclass on F that unlocks it.

Type parameters

A

focus read out of S

B

focus written back to produce T (often = A)

F

two-argument carrier; capabilities scale with the typeclasses F admits.

S

source type being observed / modified

T

result type after modification (often = S)

Attributes

See also
Companion
object
Source
Optic.scala
Graph
Supertypes
class Object
trait Matchable
class Any
Known subtypes
class AvroBridge[A, B]
class AvroPrism[A]
class AvroRecordPrism[A]
class AvroTraversal[A]
class JsonPrism[A]
class BijectionIso[S, T, A, B]
class ForgetFold[S, F, A]
class GetReplaceLens[S, T, A, B]
class Getter[S, A]
class MendTearPrism[S, T, A, B]
class Modify[S, T, A, B]
class Optional[S, T, A, B]
class PickFold[S, A]
class PickMendPrism[S, A, B]
class Review[T, B]
class SplitCombineLens[S, T, A, B, XA]
class SimpleLens[S, A, XA]
class Unfold[T, B, F]
Show all
Self type
Optic[S, T, A, B, F]

Members list

Grouped members

Operations

inline def cross[G[_, _], C, D](o: Optic[T, S, C, D, G])(using m: Morph[F, G]): Optic[B, A, C, D, m.Out]
Extension method from Optic

Flip the direction of an optic — only defined when the carrier admits both Accessor[F] and ReverseAccessor[F] (i.e. iso-shaped carriers).

Flip the direction of an optic — only defined when the carrier admits both Accessor[F] and ReverseAccessor[F] (i.e. iso-shaped carriers).

Attributes

Source
Optic.scala
def exists(p: A => Boolean)(s: S): Boolean
Extension method from Optic

True iff at least one focus satisfies p. Short-circuits on the first hit when the carrier's ForgetfulFold[F] is short-circuit-aware. The default Monoid[Boolean] cats exposes is conjunction (&&); we instantiate a disjunction monoid inline so a single match can win without consulting the rest.

True iff at least one focus satisfies p. Short-circuits on the first hit when the carrier's ForgetfulFold[F] is short-circuit-aware. The default Monoid[Boolean] cats exposes is conjunction (&&); we instantiate a disjunction monoid inline so a single match can win without consulting the rest.

Attributes

Source
Optic.scala
inline def foldMap[M : Monoid](f: A => M): S => M
Extension method from Optic

foldMap over the focus — the primary consumption path for Fold and any other carrier with a ForgetfulFold[F] instance. Combines every focus through Monoid[M].

foldMap over the focus — the primary consumption path for Fold and any other carrier with a ForgetfulFold[F] instance. Combines every focus through Monoid[M].

Attributes

Source
Optic.scala
inline def get(s: S): A
Extension method from Optic

Total read — extract the focus, available when the carrier always hits (Accessor[F]: Tuple2 for Lens, Direct for Iso / Getter). readOnly wraps the same read as a concrete Getter, the one-way view of a writable optic.

Total read — extract the focus, available when the carrier always hits (Accessor[F]: Tuple2 for Lens, Direct for Iso / Getter). readOnly wraps the same read as a concrete Getter, the one-way view of a writable optic.

Attributes

Source
Optic.scala
inline def getOption(s: S): Option[A]
Extension method from Optic

Partial read — Some(focus) on hit, None on miss. Available when the carrier may miss (PartialAccessor[F]: Either for Prism, Affine for Optional / AffineFold).

Partial read — Some(focus) on hit, None on miss. Available when the carrier may miss (PartialAccessor[F]: Either for Prism, Affine for Optional / AffineFold).

Attributes

Source
Optic.scala
def headOption(s: S): Option[A]
Extension method from Optic

First focus visible through the optic, if any. Available on any carrier admitting ForgetfulFold[F]Forget[F] (Fold), MultiFocus[F] (Traversal), Affine (Optional / AffineFold), Either (Prism), Tuple2 (Lens). For 0-or-1-focus carriers this is the same Option you'd get from .getOption; for multi-focus carriers it picks the first focus the underlying Foldable enumerates.

First focus visible through the optic, if any. Available on any carrier admitting ForgetfulFold[F]Forget[F] (Fold), MultiFocus[F] (Traversal), Affine (Optional / AffineFold), Either (Prism), Tuple2 (Lens). For 0-or-1-focus carriers this is the same Option you'd get from .getOption; for multi-focus carriers it picks the first focus the underlying Foldable enumerates.

Implemented via foldMap under a custom "first-Some" Monoid[Option[A]] — the same shape Monocle's Fold.headOption uses. The custom monoid short-circuits via _.orElse(_), so on a Foldable[F] whose foldMap is itself short-circuit-aware (e.g. LazyList) the walk stops at the first focus.

Attributes

Source
Optic.scala
def length(s: S): Int
Extension method from Optic

Number of foci visible through the optic. O(n) in the focus count via Foldable.foldMap under Monoid[Int]. Distinct from Foldable.size only in that it routes through the carrier's ForgetfulFold[F] — same end result, available wherever foldMap is.

Number of foci visible through the optic. O(n) in the focus count via Foldable.foldMap under Monoid[Int]. Distinct from Foldable.size only in that it routes through the carrier's ForgetfulFold[F] — same end result, available wherever foldMap is.

Attributes

Source
Optic.scala
inline def modify(f: A => B): S => T
Extension method from Optic

Modify (A => B) or replace (by constant B) the focus in-place. Available for every carrier with a ForgetfulFunctor[F] instance — i.e. every current optic family.

Modify (A => B) or replace (by constant B) the focus in-place. Available for every carrier with a ForgetfulFunctor[F] instance — i.e. every current optic family.

Attributes

Source
Optic.scala
inline def modifyA[G[_]](f: A => G[B])(using G: Applicative[G]): S => G[T]
Extension method from Optic

Effectful modify over any Applicative[G]; unlike modifyF this variant also exposes all, which collects every visited focus via Applicative[List].

Effectful modify over any Applicative[G]; unlike modifyF this variant also exposes all, which collects every visited focus via Applicative[List].

Attributes

Source
Optic.scala
inline def modifyF[G[_]](f: A => G[B])(using G: Functor[G]): S => G[T]
Extension method from Optic

Effectful modify over any Functor[G] — generalises modify to monadic / effectful A => G[B] transformations.

Effectful modify over any Functor[G] — generalises modify to monadic / effectful A => G[B] transformations.

Attributes

Source
Optic.scala
inline def place(b: B): T => T
Extension method from Optic

Overwrite a T-shaped value at the focus — available when the carrier can witness T => F[X, B] (e.g. Direct, where F[X, B] = B). transfer lifts a C => B into this same shape with an extra C argument.

Overwrite a T-shaped value at the focus — available when the carrier can witness T => F[X, B] (e.g. Direct, where F[X, B] = B). transfer lifts a C => B into this same shape with an extra C argument.

Attributes

Source
Optic.scala
inline def put(f: A => B): A => T
Extension method from Optic

Construct a T directly from an A, running f: A => B at the focus — available for carriers with a ForgetfulApplicative[F] instance (today: Direct).

Construct a T directly from an A, running f: A => B at the focus — available for carriers with a ForgetfulApplicative[F] instance (today: Direct).

Attributes

Source
Optic.scala
inline def readOnly: Getter[S, A]
Extension method from Optic

Total read — extract the focus, available when the carrier always hits (Accessor[F]: Tuple2 for Lens, Direct for Iso / Getter). readOnly wraps the same read as a concrete Getter, the one-way view of a writable optic.

Total read — extract the focus, available when the carrier always hits (Accessor[F]: Tuple2 for Lens, Direct for Iso / Getter). readOnly wraps the same read as a concrete Getter, the one-way view of a writable optic.

Attributes

Source
Optic.scala
inline def replace(b: B): S => T
Extension method from Optic

Modify (A => B) or replace (by constant B) the focus in-place. Available for every carrier with a ForgetfulFunctor[F] instance — i.e. every current optic family.

Modify (A => B) or replace (by constant B) the focus in-place. Available for every carrier with a ForgetfulFunctor[F] instance — i.e. every current optic family.

Attributes

Source
Optic.scala
def reverse: Optic[B, A, T, S, F]
Extension method from Optic

Flip the direction of an optic — only defined when the carrier admits both Accessor[F] and ReverseAccessor[F] (i.e. iso-shaped carriers).

Flip the direction of an optic — only defined when the carrier admits both Accessor[F] and ReverseAccessor[F] (i.e. iso-shaped carriers).

Attributes

Source
Optic.scala
inline def reverseGet(b: B): T
Extension method from Optic

Build the "no context" reverse — takes a fresh B and produces the corresponding T. Available when the carrier has a ReverseAccessor[F] instance (today: Either and Direct).

Build the "no context" reverse — takes a fresh B and produces the corresponding T. Available when the carrier has a ReverseAccessor[F] instance (today: Either and Direct).

Attributes

Source
Optic.scala
inline def transfer[C](f: C => B): T => C => T
Extension method from Optic

Overwrite a T-shaped value at the focus — available when the carrier can witness T => F[X, B] (e.g. Direct, where F[X, B] = B). transfer lifts a C => B into this same shape with an extra C argument.

Overwrite a T-shaped value at the focus — available when the carrier can witness T => F[X, B] (e.g. Direct, where F[X, B] = B). transfer lifts a C => B into this same shape with an extra C argument.

Attributes

Source
Optic.scala
inline def transform(f: D => B): T => T
Extension method from Optic

Generalised place: transform a D at the focus via D => B rather than replacing unconditionally.

Generalised place: transform a D at the focus via D => B rather than replacing unconditionally.

Attributes

Source
Optic.scala
inline def cross[C, D](o: Optic[T, S, C, D, F])(using Accessor[F], ReverseAccessor[F]): Optic[B, A, C, D, F]

Build-then-observe across the build-output ⇄ read-input seam, preserving structure, on a shared carrier F. Flip self (it must be reversible — Accessor[F] and ReverseAccessor[F], i.e. an Iso or Review over Direct) so it reads T from B, then andThen that under the same carrier. The result is the full Optic[B, A, C, D, F], not a collapsed getter: its read capability follows the carrier (.get for Direct), and self's read focus A survives as the composite's write-back focus.

Build-then-observe across the build-output ⇄ read-input seam, preserving structure, on a shared carrier F. Flip self (it must be reversible — Accessor[F] and ReverseAccessor[F], i.e. an Iso or Review over Direct) so it reads T from B, then andThen that under the same carrier. The result is the full Optic[B, A, C, D, F], not a collapsed getter: its read capability follows the carrier (.get for Direct), and self's read focus A survives as the composite's write-back focus.

This is exactly self.reverse.andThen(that). The motivating case is ana.cross(cata): a Review (the unfold) crossed with a getter on the built S (the fold) — a (materializing) hylomorphism whose .get reads the folded value. When that sits on a different carrier (a Prism, a Fold, …), the cross-carrier cross overload below is selected instead.

Seam: that's source is self's T and its back-type is self's S.

Attributes

Source
Optic.scala

Type members

Types

type X

Existential leftover carried alongside the focus — the type-level witness the carrier uses to rebuild T. Concrete at construction (Lens.apply sets X = S, Prism.apply sets X = S, …) and abstract when the optic is bound to Optic[…, F] without refinement.

Existential leftover carried alongside the focus — the type-level witness the carrier uses to rebuild T. Concrete at construction (Lens.apply sets X = S, Prism.apply sets X = S, …) and abstract when the optic is bound to Optic[…, F] without refinement.

Attributes

Source
Optic.scala

Value members

Abstract methods

def from(b: F[X, B]): T

Close the carrier: given a modified focus B (and the leftover X already inside the F), reassemble the result T.

Close the carrier: given a modified focus B (and the leftover X already inside the F), reassemble the result T.

Attributes

Source
Optic.scala
def to(s: S): F[X, A]

Push the source S into the carrier, extracting the focus A and packing the leftover X. Paired with from to reconstruct T.

Push the source S into the carrier, extracting the focus A and packing the leftover X. Paired with from to reconstruct T.

Attributes

Source
Optic.scala

Concrete methods

inline def all(s: S): List[F[o.X, A]]
Extension method from Optic

Every visited focus, still inside its carrier (List[F[o.X, A]]) — the raw-optic counterpart of the carrier-free CanFold.foci.

Every visited focus, still inside its carrier (List[F[o.X, A]]) — the raw-optic counterpart of the carrier-free CanFold.foci.

Attributes

Source
Optic.scala
inline def andThen[G[_, _], C, D](o: Optic[A, B, C, D, G])(using m: Morph[F, G]): Optic[S, T, C, D, m.Out]
Extension method from Optic

Cross-carrier .andThen — picks the direction via a summoned compose.Morph when the two optics' carriers differ. With one exception (forget2multifocus / multifocus2forget), cats-eo ships no bidirectional Composer pairs, so at most one Morph applies per carrier pair. That one pair can make a Forget[F]MultiFocus[F] chain ambiguous; it's resolved via the explicit Composer[..].to(o) form (see multifocus2forget).

Cross-carrier .andThen — picks the direction via a summoned compose.Morph when the two optics' carriers differ. With one exception (forget2multifocus / multifocus2forget), cats-eo ships no bidirectional Composer pairs, so at most one Morph applies per carrier pair. That one pair can make a Forget[F]MultiFocus[F] chain ambiguous; it's resolved via the explicit Composer[..].to(o) form (see multifocus2forget).

Attributes

Example
lens[Person](_.phones)
 .andThen(Traversal.each[ArraySeq, Phone])
 .andThen(lens[Phone](_.isMobile))
Source
Optic.scala
inline def andThen[D](o: Review[B, D]): Review[T, D]
Extension method from Optic

ANY reversible outer ∘ build-only inner — only the two BUILD sides matter, so the composite collapses to a Review (reverseGet ∘ reverseGet), the build-direction mirror of the read-only andThen collapse.

ANY reversible outer ∘ build-only inner — only the two BUILD sides matter, so the composite collapses to a Review (reverseGet ∘ reverseGet), the build-direction mirror of the read-only andThen collapse.

Attributes

Source
Optic.scala
inline def andThen[G[_], D](o: Unfold[B, D, G]): Unfold[T, D, G]
Extension method from Optic

ANY reversible outer ∘ build-only-many inner — the inner Unfold assembles the focus B from a layer F[D], and this optic's build half re-homes it to T, so the composite is an Unfold[T, D, F] (reverseGet ∘ embed). The many-rung mirror of the andThen(Review) overload above; fires for Iso / Prism outers (Review outers resolve to the fused Review.andThen member first).

ANY reversible outer ∘ build-only-many inner — the inner Unfold assembles the focus B from a layer F[D], and this optic's build half re-homes it to T, so the composite is an Unfold[T, D, F] (reverseGet ∘ embed). The many-rung mirror of the andThen(Review) overload above; fires for Iso / Prism outers (Review outers resolve to the fused Review.andThen member first).

Attributes

Source
Optic.scala
inline def andThen[C, D](o: Modify[A, B, C, D]): Modify[S, T, C, D]
Extension method from Optic

ANY writable outer ∘ write-only inner — the inner cannot be read, so the composite is write-only too: a Modify running modify(o.modify(f)).

ANY writable outer ∘ write-only inner — the inner cannot be read, so the composite is write-only too: a Modify running modify(o.modify(f)).

Attributes

Source
Optic.scala
inline def andThen[C, D](o: Optic[A, B, C, D, F]): Optic[S, T, C, D, F]

Compose with another optic under the shared carrier F. Requires AssociativeFunctor[F]. Cross-carrier composition (Lens → Optional, Lens → Traversal, …) goes through the Morph-summoning overload of this same method.

Compose with another optic under the shared carrier F. Requires AssociativeFunctor[F]. Cross-carrier composition (Lens → Optional, Lens → Traversal, …) goes through the Morph-summoning overload of this same method.

Attributes

Example
case class Address(street: String)
case class Person(address: Address)
val streetLens = lens[Person](_.address).andThen(lens[Address](_.street))
Source
Optic.scala
def andThen[C, IB, G[_, _]](inner: Optic[A, Unit, C, IB, G])(using rc: ReadCompose[F, G]): rc.Out[S, C]

ANY outer ∘ read-only inner — the inner is honestly one-way (T = Unit: a Getter, AffineFold, or Fold), so only the two READ sides matter and the composite collapses to the read-only join of their strengths via compose.ReadCompose (Getter / PickFold / ForgetFold).

ANY outer ∘ read-only inner — the inner is honestly one-way (T = Unit: a Getter, AffineFold, or Fold), so only the two READ sides matter and the composite collapses to the read-only join of their strengths via compose.ReadCompose (Getter / PickFold / ForgetFold).

A trait member (not an extension in the companion) deliberately: once a receiver is statically one of the fused concrete classes, its andThen member overloads enter resolution and Scala 3 never falls back to extension methods when they all fail — the collapse must be in the member overload set to be reachable without an expected-type ascription.

Only the inner's T is pinned to Unit; its B stays free (IB) even though read-only inners always have B = Unit. That keeps this overload strictly LESS specific than the same-carrier andThen above (which accepts every argument this one does whenever B = Unit at the receiver), so a same-carrier read-only ∘ read-only call resolves unambiguously to the AssociativeFunctor path and this one fires exactly on the cross-seam cells the generic member cannot type.

Attributes

Source
Optic.scala
def morph[G[_, _]](using cf: Composer[F, G]): Optic[S, T, A, B, G]
Extension method from Optic

Re-express this optic over a different carrier G via the summoned Composer[F, G]. Ordinary code composes cross-carrier through andThen above (which morphs implicitly); the explicit form serves law / behaviour specs and the rare Morph-ambiguous chain.

Re-express this optic over a different carrier G via the summoned Composer[F, G]. Ordinary code composes cross-carrier through andThen above (which morphs implicitly); the explicit form serves law / behaviour specs and the rare Morph-ambiguous chain.

Attributes

Source
Optic.scala
inline def writeOnly: Review[T, B]
Extension method from Optic

The build half alone as a concrete Review — the write-direction dual of readOnly.

The build half alone as a concrete Review — the write-direction dual of readOnly.

Attributes

Source
Optic.scala