Indexed

dev.constructive.eo.optics.Indexed
See theIndexed companion object
trait Indexed[S, T, A, B, I] extends Optic[S, T, A, B, GlassF[I]]

Standalone prototype of the full-grid successor to MultiFocus[Function1[X0, *]] (docs/research/2026-10-05-multifocus-redesign.md): an Optic whose focus is a tabulation over a fixed index, so composition takes the PRODUCT index and reads every nested coordinate, not a shared-index diagonal.

It is an ordinary member of the Optic family — carrier GlassF[I], leftover X. Constructors implement to / from; nothing else stores state.

A lawful glass is an isomorphism S (X, I => A): from(to(s)) recovers the source, and to(from(bundle)) recovers the context and supplied tabulation pointwise — including bundles not obtained from to. Polymorphic glasses require the corresponding target-side decomposition to state the second law. Context equality, like function equality, is observational; it need not be Scala ==.

Implementations and tabulations must be pure. Composition derives inner contexts lazily at each outer index, with no enumeration, coordinate comparison, sampling, or memoization. An empty index is therefore valid. The laws are obligations on constructors, not enforced by this trait's types.

Deliberately ABSENT while this is a prototype: no AssociativeFunctor[GlassF[I]] (same-index composition would force the diagonal this family exists to escape), no inbound/outbound bridges to other carriers, and no zip/collect — those are context-sensitive operations whose honest home is the X = Unit specialization or a later design.

Type parameters

I

the fixed index type; at, modify, replace and the composed grid all address it.

Attributes

Companion
object
Source
Indexed.scala
Graph
Supertypes
trait Optic[S, T, A, B, GlassF[I]]
class Object
trait Matchable
class Any
Self type
Indexed[S, T, A, B, I]

Members list

Grouped members

Operations

inline def cross[C, D](o: Optic[T, S, C, D, GlassF[I]])(using Accessor[GlassF[I]], ReverseAccessor[GlassF[I]]): Optic[B, A, C, D, GlassF[I]]

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

Inherited from:
Optic
Source
Optic.scala

Coordinate-addressable operations

def at(i: I): S => A

Read one coordinate. This is not a single-coordinate setter: arbitrary indexes do not come with a notion of coordinate equality.

Read one coordinate. This is not a single-coordinate setter: arbitrary indexes do not come with a notion of coordinate equality.

Attributes

Source
Indexed.scala
def modify(f: (I, A) => B): S => T

Rewrite every coordinate with its own index visible to the function, preserving the context returned by this source's to.

Rewrite every coordinate with its own index visible to the function, preserving the context returned by this source's to.

Attributes

Source
Indexed.scala
def replace(value: B): S => T

Broadcast to the whole index space, including every coordinate of a composed grid.

Broadcast to the whole index space, including every coordinate of a composed grid.

Attributes

Source
Indexed.scala

Type members

Inherited and Abstract 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

Inherited from:
Optic
Source
Optic.scala

Value members

Concrete methods

def andThen[C, D, J](inner: Indexed[A, B, C, D, J]): Aux[S, T, C, D, (I, J), (X, I => inner.X)]

Full-grid composition. Index and context reassociation, rather than literal type equality, relate the two groupings of a three-level composition. Unit axes are not normalized away.

Full-grid composition. Index and context reassociation, rather than literal type equality, relate the two groupings of a three-level composition. Unit axes are not normalized away.

Round-trip proof for lawful monomorphic components: write outer.to(s) = (x, a) and inner.to(a(i)) = (y(i), c(i)). Applying from to this bundle gives outer.from((x, i => inner.from((y(i), c(i))))) = outer.from((x, a)) = s. Conversely, for ANY (x, y) and grid w, from uses rows b(i) = inner.from((y(i), j => w(i,j))). The outer law recovers (x, b); the inner law recovers (y(i), j => w(i, j)) at every i, so the composite recovers ((x, y), w). No context obtained from an earlier grid is cached. Both three-level groupings reconstruct as outer.from((x, i => middle.from((y(i), j => inner.from((z(i,j), k => w(i,j,k))))))); their to operations recover the same contexts and focus after reassociation. The same argument applies to paired source/target algebras for polymorphic composition.

Attributes

Source
Indexed.scala

Inherited methods

def andThen[C, IB, G[_, _]](inner: Optic[A, Unit, C, IB, G])(using rc: ReadCompose[GlassF[I], 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

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

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))
Inherited from:
Optic
Source
Optic.scala

Inherited and Abstract methods

def from(b: GlassF[I][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

Inherited from:
Optic
Source
Optic.scala
def to(s: S): GlassF[I][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

Inherited from:
Optic
Source
Optic.scala