Unfold

dev.constructive.eo.optics.Unfold
See theUnfold companion object
final class Unfold[T, B, F[_]] extends Optic[Unit, T, Unit, B, Forget[F]]

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.

Attributes

Companion
object
Source
Unfold.scala
Graph
Supertypes
trait Optic[Unit, T, Unit, B, Forget[F]]
class Object
trait Matchable
class Any

Members list

Grouped members

Operations

inline def cross[C, D](o: Optic[T, Unit, C, D, Forget[F]])(using Accessor[Forget[F]], ReverseAccessor[Forget[F]]): Optic[B, Unit, C, D, Forget[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

Inherited from:
Optic
Source
Optic.scala

Type members

Types

type X = Nothing

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
Unfold.scala

Value members

Concrete methods

inline def andThen[D](inner: Review[B, D])(using F: Functor[F]): Unfold[T, D, F]

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.

Attributes

Source
Unfold.scala
inline def andThen[D](inner: Unfold[B, D, F])(using F: Applicative[F]): Unfold[T, D, F]

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.

Attributes

Source
Unfold.scala
inline def andThen[G[_, _], D](inner: Optic[_, B, _, D, G])(using ra: ReverseAccessor[G], F: Functor[F]): Unfold[T, D, F]

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.

Attributes

Source
Unfold.scala
def from(fb: Forget[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
Unfold.scala
def to(u: Unit): Forget[F][X, Unit]

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
Unfold.scala

Inherited methods

def andThen[C, IB, G[_, _]](inner: Optic[Unit, Unit, C, IB, G])(using rc: ReadCompose[Forget[F], G]): rc.Out[Unit, 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[Unit, B, C, D, Forget[F]]): Optic[Unit, T, C, D, Forget[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))
Inherited from:
Optic
Source
Optic.scala

Concrete fields

val embed: F[B] => T

Attributes

Source
Unfold.scala