The value-level computation type former (`Comp {e} a`)
Status: design settled 2026-10-28 (D327). Implementation rides the P33 streaming slice; this note is the decision record.
The problem
Cinch puts effects on arrows: read-line :: Handle ->{IO r, Fs r} String. A function arrow’s row says what applying the function may
do; handle ... with interprets the operations of a computation.
What the language lacks is a TYPE for “a computation, as a value”:
something a data structure can carry, so traversal targets can hold
effectful steps. The concrete demand is P33: a pull-based Stream
over IO sources where each step is an effectful read. A Stream a
of pure values cannot express “the next element requires IO r to
produce”; the stream’s elements must be effectful computations, or
the stream type itself must carry the row. The A7 backlog called the
missing piece a “value-level computation type former for effect-row
traversal targets (Comp {e} a-like)”.
Options
Comp {e} aas a library newtype over effectful unit closures:newtype Comp (e :: Effect) (a :: Type) = MkComp (Unit ->{e} a)withsuspend :: (Unit ->{e} a) -> Comp {e} aandrun :: Comp {e} a ->{e} a. The type former exists at the kind level (Comp :: Effect -> Type -> Type- the row parameter is an effect-row type argument, the machinery example 11 already uses forRequest {e}), the value is an ordinary latent closure, and running it is ordinary application.- A core type former with its own intro/elim (
suspend/force) distinct from closures, carried through elaboration, codegen and the evaluator as a new form. - Rows on the container instead:
Stream {e} a-style, each traversal type carrying its own effect row, computations never becoming values.
Decision (D327): option 1
Compis ordinary prelude code (Effect/Comp.cinchhome in theEffectfamily), arepr(transparent)newtype overUnit ->{e} a. No new core forms; the type checker, CPS elaboration (D24), codegen and the D92 evaluator are untouched.- Why not 2: a core form buys runtime nothing (the closure IS the representation) and costs elaboration surface everywhere. The whole project’s taste is boring machinery over new core forms.
- Why not 3: it spreads rows across every container type and
still needs the “effectful step” type inside
Stream’s own observation;Compsolves the general problem once. - Semantics:
run (suspend k) = k ()definitionaly at the library level;suspend/runare%1-neutral (the closure’s own multiplicity governs). Running aCompis an application, so the effect dispatch discipline holds exactly as for direct calls (D24’s boundary: handled computations dispatch; lambda bodies are latent andCompvalues are lambda bodies in a box).Compvalues are ordinary immutable data: freely stored, passed, and (withShareclosures) shared. - Handlers:
handle (run c) with his the ordinary shape; aCompcarries its row in its type (Comp {IO r, Fs r} Chunk), so the row-polymorphic signature discipline applies unchanged (forall r. ... -> Comp {IO r} Chunk). - Stream integration (P33): the pull-based
Streamover IO sources yieldsComp {e} asteps (or exposesnext :: Stream e a ->{e} Option (a, Stream e a)); resource safety is the P8 codata plus the region bracket story (acquire/release in the row,Ref/Mutregion discipline). P33’s slice owns the exact Stream shape; this note only fixes whatCompis. - Refusals kept honest: no implicit sequencing of
Compvalues (they are data untilrun); no effect-row subtyping onCompbeyond the existing implication machinery if any;Compdoes not get aMonadinstance as compiler magic - if the library wantsinstance Monad (Comp {e}), it is an ordinary library instance with ordinary obligations.
What implementation owes (P33)
Effect/Comp.cinch: the newtype,suspend/run, and any instances (with the D159 home rules).Streamover IO sources usingCompfor its steps; region bracket acquire/release.- Tests pinning:
run/suspendround trip, aCompstored in a data structure and run later under a handler, and the refusal of implicit sequencing.
