Desc
A uniform interface to levitated descriptions
(Desc I k) that hides the encoder details behind a small
algebra of views, folds, and predicates. Every entry accepts
either HOAS or evaluated Val descriptions — evalDesc
normalises to Val idempotently.
descView returns a one-step semantic view exposing the outer
constructor as both an integer idx (0=ret, 1=arg, 2=rec, 3=pi,
4=plus) and a human-readable kind string, alongside the
constructor-specific fields (I, k, j, sTy, tFn, sub,
A, B, plus optional label/conLabel sidecar metadata).
foldDesc is the catamorphism: pass per-constructor handler
functions, each receiving a record with the recursed sub-values
already materialised. foldDescM is the monadic dual — each
handler returns a Computation R and the combinator binds
sub-results before invoking the matching case; paraDM is the
paramorphic variant that additionally exposes the original
sub-description Val at each recursive site (for callers that
consult kernel-level shape without paying for a monadic
descent). foldDescWithPath mirrors foldDescM and threads a
[fx.diag.positions] chain extended at each descent so handlers
can construct positionally-blamed errors directly from inside
the fold. mapDesc is the structural map — handlers return
either replacement HOAS, an explicit { _replaceChildren = …; }
payload, or a Val for direct substitution; identity mappers
reconstruct definitionally-equal outputs via the canonical
encoder chain.
deepEqualDesc decides definitional equality by delegating to
fx.tc.conv.conv; presentation labels (_label, _conLabel)
are conv-irrelevant. children, shape, and the isRet /
isArg / isRec / isPi / isPlus predicates round out the
introspection surface for callers that want lightweight
structural queries without writing a full fold.
children
children: direct sub-descriptions list — empty for ret, [body] for arg/rec/pi (arg applies the body to a vTt placeholder), [A, B] for plus.
children : Hoas | Val -> [Val]
deepEqualDesc
deepEqualDesc: definitional equality on description values — evaluates both arguments via evalDesc, then runs fx.tc.conv.conv 0 for full conv-equality.
deepEqualDesc : Hoas | Val -> Hoas | Val -> Bool
descView
descView: one-step semantic view of a description value — returns { idx, kind, I, k, ... } selecting the outer constructor (0 = ret, 1 = arg, 2 = rec, 3 = pi, 4 = plus); throws on malformed inputs.
descView : Hoas | Val -> { idx : 0..4; kind : String; I : Val; k : Val; ... }
Friendly wrapper around fx.tc.eval.descView that adds a
human-readable kind field alongside the integer idx.
Returns the raw view fields (I, k, j, sTy, tFn,
sub, A, B, optional label/conLabel) so callers can
peel apart any description shape uniformly.
Use this entry as the canonical view for generic programming
over levitated descriptions; the lower-level
fx.tc.eval.descView is identical in semantics but skips the
kind-naming step.
evalDesc
evalDesc: idempotent description normaliser — returns evaluated Val descriptions unchanged, evaluates HOAS descriptions via H.elab + E.eval []; throws on inputs that are neither.
evalDesc : Hoas | Val -> Val
foldDesc
foldDesc: catamorphism over description structure — recursively decomposes a description into its ret/arg/rec/pi/plus shape and invokes the matching case in cases with the materialised sub-values.
foldDesc : { ret? ; arg? ; rec? ; pi? ; plus? ; default? } -> Hoas | Val -> R
foldDescM
foldDescM: monadic catamorphism over description structure — recurses through ret/arg/rec/pi/plus and binds each sub-Computation R before invoking the matching handler, so handlers receive already-bound R carriers and the combinator owns the threading.
foldDescM : { ret? ; arg? ; rec? ; pi? ; plus? ; default? } -> Hoas | Val -> Computation R
Monadic dual of foldDesc. Each handler returns a
Computation R; recursed sub-computations are sequenced via
fx.kernel.bind before the handler runs, so a handler
observes bound R values (not raw computations).
Per-summand handler shapes:
ret:{ view; j }— no sub-recursion.arg:{ view; sTy; body; sample }wherebody : arg -> Computation Rdefers recursion at the body's parameter andsample : Ris the bound result of recursing on the placeholder-instantiated body (mirrors the purefoldDescarg convention).rec:{ view; j; sub }withsub : R.pi:{ view; sTy; fn; sub }withsub : R(the selectorfnstays raw — it's aVal, not a description).plus:{ view; left; right }with both bound.
The default handler is used when a per-summand handler is
absent; default-of-defaults is _: K.pure null.
Use this fold to drive checkD/inferD-shaped walkers where
each kernel-CHECK sub-delegation lives inside a
Computation Tm (typeError handler, bindP-style position
wrapping). The combinator preserves the pure-bind discipline:
recursing at a summand whose sub-walk resolves to K.pure …
does not install a handler.
foldDescWithPath
foldDescWithPath: monadic catamorphism that threads a path of fx.diag.positions segments AND uses bindP at each descent so emitted typeErrors auto-wrap under the structural position before re-raising.
foldDescWithPath : [Position] -> { ret? ; arg? ; rec? ; pi? ; plus? ; default? } -> Hoas | Val -> Computation R
Variant of foldDescM with two-pronged structural blame:
every descent installs a fx.tc.check.bindP handler under
the descent's position, and the current path is exposed to
handlers as data. The blame alphabet at each descent:
arg.T → P.DArgBody
rec.D → P.DRecTail
pi.T → P.DPiBody
plus.L → P.DPlusL
plus.R → P.DPlusR
Because the descent uses bindP, a sub-handler that emits
typeError produces an error tree whose outermost edge is
the descent position, with the original error nested under
it. Nested descents stack — a typeError emitted at the leaf
of a plus(rec(ret)) descent surfaces with the edge chain
[DPlusL, DRecTail] outermost-first, matching the kernel's
existing positional-blame convention. Handlers that prefer
explicit position wrapping can use the path argument and
D.nestUnder directly; both routes compose because the
alphabet is identical.
Pure (K.pure …) sub-walks short-circuit through bindP's
isPure fast path — no handler install, no overhead.
Each handler receives the current path (the chain leading
to its node, not yet extended by its own descent) and, for
non-leaf summands, helper extenders matching what the
descent will install:
ret:{ view; path; j }.arg:{ view; path; sTy; body; sample; bodyPath }—bodyPath = path ++ [DArgBody].rec:{ view; path; j; sub; subPath }—subPath = path ++ [DRecTail].pi:{ view; path; sTy; fn; sub; subPath }—subPath = path ++ [DPiBody].plus:{ view; path; left; right; leftPath; rightPath }.
Pass [] as the root path for a top-level walk. Nested
callers pass the outer chain so positions compose
end-to-end.
isArg
isArg: predicate — true iff the description's outer constructor is descArg.
isArg : Hoas | Val -> Bool
isPi
isPi: predicate — true iff the description's outer constructor is descPi.
isPi : Hoas | Val -> Bool
isPlus
isPlus: predicate — true iff the description's outer constructor is plusI (binary sum).
isPlus : Hoas | Val -> Bool
isRec
isRec: predicate — true iff the description's outer constructor is descRec.
isRec : Hoas | Val -> Bool
isRet
isRet: predicate — true iff the description's outer constructor is descRet.
isRet : Hoas | Val -> Bool
mapDesc
mapDesc: structurally rewrite each layer of a description via per-shape mapper functions, then reconstruct using the canonical encoder chain — the result is conv-equivalent to the original when mappers are identities.
mapDesc : { ret? ; arg? ; rec? ; pi? ; plus? } -> Hoas | Val -> Val
paraDM
paraDM: monadic paramorphism over description structure — each handler additionally receives the original sub-description Val alongside the bound recursed result, enabling certificate-aware fast paths and reflective elaborators.
paraDM : { ret? ; arg? ; rec? ; pi? ; plus? ; default? } -> Hoas | Val -> Computation R
Paramorphic variant of foldDescM. Where foldDescM exposes
only the recursed R carrier at each sub-position, paraDM
also exposes the raw sub-description Val — required by
callers that inspect the kernel-level description shape
without paying for a monadic descent (certificate consultation,
ornament-section selection, reflection).
Per-summand handler shapes:
ret:{ view; j }.arg:{ view; sTy; body; bodyDesc; sample; sampleDesc }—body : arg -> Computation Rand the parallelbodyDesc : arg -> Valreturn the sub-description without recursing.sampleandsampleDescare the bound result and rawValat the placeholder respectively.rec:{ view; j; sub; subDesc }—subDesc : Valis the original sub-descriptionview.sub.pi:{ view; sTy; fn; sub; subDesc }.plus:{ view; left; right; leftDesc; rightDesc }.
default and identity-on-failure semantics match foldDescM.
shape
shape: tagged-record skeleton of a description — returns { kind = "ret" | "arg" | "rec" | "pi" | "plus"; ...payload } with only the shape-relevant fields; payload depends on kind.
shape : Hoas | Val -> { kind : String; ... }