Build-only counterpart to Fold — the inhabitant of the build-only / many cell of the optic lattice, exactly as Review is Getter's build-only mirror on the total rung. Fold is Optic[S, Unit, A, Unit, Forget[F]] (a real to reading S => F[A], vestigial from); Unfold is the across-both-axes dual Optic[Unit, T, Unit, B, Forget[F]] — a vestigial to and a real from that assembles one T from a layer of parts F[B]:
embed : F[B] => T // many → one: the algebra of a recursion scheme
This is the F-shape embed of a Corecursive instance (Fold's S => F[A] being the project half), and the aggregation arrow "build an Order from its line-items". Plated's plate bundles both halves inside one read-write traversal; Unfold exposes the embed half standalone, so an algebra can be carried, composed, and consumed as an optic.
'''The vestigial to.''' Read-only optics zero out their write side for free (from discards into Unit, the terminal object). The build-only dual is not free: to: Unit => F[Unit] must produce an F-layer, and there is no canonical F[Unit] without Applicative[F] (pure(())). Two constructors, two answers:
Unfold.apply (F: Applicative) — honest vestigial to = pure(()). Read-side operations that reach to (.modify, .foldMap, …) degrade to the singleton layer.
Unfold.algebra (no constraint) — for pattern functors (BinF, RoseF, …), which admit Functor/Traverse but no Applicative (pure cannot pick a constructor). Its to is genuinely unreachable through the build-only surface and THROWS if forced — the same reachable-path contract as Composer.direct2forget's singleton-pick from.
A final class storing embed directly — NOT an abstract member — per the composed-dispatch findings on Getter / Review. Fused andThen members keep build-only chains concrete.
Build-then-observe across the build-output ⇄ read-input seam, preserving structure, on a shared carrierF. Flip self (it must be reversible — Accessor[F]andReverseAccessor[F], i.e. an Iso or Review over Direct) so it reads T from B, then andThenthat 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 carrierF. Flip self (it must be reversible — Accessor[F]andReverseAccessor[F], i.e. an Iso or Review over Direct) so it reads T from B, then andThenthat 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.
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.
Fused Unfold.andThen(Review) — pre-process each part. inner builds the part B from a D, so the composite assembles T from a layer of Ds: embed ∘ map(inner.reverseGet). Needs only Functor[F], so it is available to pattern-functor algebras. inline for the same per-level-lambda reason as Getter.andThen / Review.andThen.
Fused Unfold.andThen(Review) — pre-process each part. inner builds the part B from a D, so the composite assembles T from a layer of Ds: embed ∘ map(inner.reverseGet). Needs only Functor[F], so it is available to pattern-functor algebras. inline for the same per-level-lambda reason as Getter.andThen / Review.andThen.
Fused Unfold.andThen(Unfold) — layered algebra composition on a shared F. inner assembles the part B from its own layer F[D]; the composite assembles T from that layer by re-lifting the single assembled B via pure — the same algebraic-lens pull as assocForgetMonad (from_outer ∘ pure ∘ from_inner), so Applicative[F] is required and pattern functors are excluded, exactly as they are from same-carrier Fold.andThen.
Fused Unfold.andThen(Unfold) — layered algebra composition on a shared F. inner assembles the part B from its own layer F[D]; the composite assembles T from that layer by re-lifting the single assembled B via pure — the same algebraic-lens pull as assocForgetMonad (from_outer ∘ pure ∘ from_inner), so Applicative[F] is required and pattern functors are excluded, exactly as they are from same-carrier Fold.andThen.
Build-only outer ∘ ANY reversible inner — an Unfold only builds, so the inner's read side is irrelevant and only its build half is threaded: each part D is mended to a B via reverseGet (available when G admits a ReverseAccessor: Direct for Iso, Either for Prism), then the layer embeds. The build-direction mirror of Getter's andThenReadAny; the fused andThen(Review) member above stays as the more-specific fast path.
Build-only outer ∘ ANY reversible inner — an Unfold only builds, so the inner's read side is irrelevant and only its build half is threaded: each part D is mended to a B via reverseGet (available when G admits a ReverseAccessor: Direct for Iso, Either for Prism), then the layer embeds. The build-direction mirror of Getter's andThenReadAny; the fused andThen(Review) member above stays as the more-specific fast path.
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.
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))