Exact Support: Instances and Lift Transport #
The instance side of the exact-support layer: the MonadLiftT m SetM compatibility shim,
transport of the judgments along a lawful monad lift, the MonadAttach / ExactMonadAttach
instances for Except, SetM, and Mathlib's WriterT, the exactness instances
for Id, Option, OptionT, and ExceptT, and the per-monad CanReturn unfoldings. The
judgments and their structural laws live in PolyFun.Control.Monad.Support; the per-run
support of StateT and ReaderT lives in PolyFun.Control.Monad.Support.Indexed.
Recovering the MonadLiftT presentation #
MonadAttach is the canonical interface for reachability here: it is core's, it carries
a lawfulness hierarchy, and core supplies instances for the transformers this library
cares about. The MonadLiftT m SetM spelling below is a compatibility shim for a
downstream still phrased that way, not the recommended API — register it locally when
migrating, rather than building against it.
Two things this does not say. SetM remains perfectly good as a carrier:
support : Set α is unchanged, and PFunctor.FreeM.support_eq_liftM_univ — which
genuinely folds into SetM as a monad — stays. What is being demoted is the lift as an
interface. And unlike the probability layer's PMF retirement, there is no upstream
force here: SetM is not being deprecated by Mathlib. This is a project standardizing
on core's vocabulary, nothing more.
One concrete argument for the direction, which is otherwise recorded nowhere:
support_eq_liftM_univ is restricted to {γ : Type uB}, because FreeM.liftM pins the
payload universe to the direction universe. MonadAttach.support on FreeM P carries
no such restriction. The attach-based presentation is strictly more universe-polymorphic
than the fold.
The two declarations are deliberately not instances, so that support reasoning does not perturb monad-lift instance search.
The support lift is lawful. Not an instance.
Transport along a monad lift #
Core proves the elimination half — lifting cannot create possible outputs — so a lift can only shrink the support, and a demonic obligation therefore transfers along it for free.
The introduction half is not available generically, and cannot be: nothing in
MonadLiftT or its lawfulness class says the lift preserves reachability, and a lift
into a monad whose CanReturn is uniformly False satisfies every law while losing
every output. A caller that needs support (liftM x) = support x must supply that
equation for its particular lift; FreeM's powerset fold
(PFunctor.FreeM.support_eq_liftM_univ) is the worked instance.
Lifting cannot create possible outputs. The set form of core's
LawfulMonadAttach.canReturn_liftM_imp'.
A demonic guarantee survives lifting: the lifted computation has no outputs the original did not have, so a property of all of the original's outputs holds of all of the lift's.
Dually, an angelic fact about the lift transfers back to the original.
And a "never" guarantee survives lifting.
Base instances #
Core supplies MonadAttach and LawfulMonadAttach for Id, Option, OptionT,
ExceptT, StateT, and ReaderT; only the exactness fields are needed here. Except and
SetM have no core instance and are supplied below.
Core provides no MonadAttach (Except ε) at this pin, only the transformer version; this
mirrors core's Option instance. An identical declaration has landed upstream and ships in
Lean v4.35, so delete this instance and the one below it at that toolchain bump.
Core's MonadAttach (ExceptT ε m) is stated at max-joined universes, which blocks
synthesis in a universe-polymorphic context; this alias instantiates it at a single
universe. Delete once the upstream declaration is repaired.
The powerset monad is its own support.
Per-monad unfoldings #
Each base monad's CanReturn is a concrete predicate, so membership in its support
has a concrete spelling. Every one of these is Iff.rfl; naming them keeps callers
from reaching through support and CanReturn with change.
The writer transformer #
WriterT ω m accumulates an output alongside the value, so the honest reading of its
support is the one that keeps that output: a value is possible exactly when it is
returned together with some accumulator. This is the same rule the measure semantics
uses for the same transformer — a writer computation denotes the underlying m (α × ω)
rather than discarding ω — and unlike StateT it costs nothing, because there is no
input index to choose. Both introduction rules survive: pure writes the unit, and
two composable outputs compose with their accumulators multiplied.
Both introduction rules hold: pure writes the unit accumulator, and composable
outputs compose with their accumulators multiplied. This is what StateT cannot have —
there is no input index to quantify over, so nothing is flattened away.