Monadic Support and Exact Composition #
Lean core's MonadAttach already provides the notion this layer needs: a predicate
MonadAttach.CanReturn x a, meaning a is a possible return value of x, together with
attach, which decorates a computation's results with proofs of that predicate.
LawfulMonadAttach further pins CanReturn down as the strongest postcondition. This
file adds a Set-valued view and optional composition laws:
ExactMonadAttach— additional introduction rules forCanReturn. Together with core's elimination rules they give exact support equations forpureandbind. These are properties of the monad, not a choice of a more precise return predicate. For example, the constant monadfun _ => PUnitforgets every result and has canonical empty support, even forpure; it is lawful but does not satisfy these extra laws.MonadAttach.support x : Set α— theSet-valued view ofCanReturn, definitionally the predicate itself, soa ∈ support x ↔ CanReturn x aisIff.rfl.- The qualitative judgments
AllOutputs("always"),SomeOutput, andNoOutput("never"), with scoped notationx ⊨ₐ p,x ⊨ₛ p, andx ⊭ p.
ExactMonadAttach is a proof-only extension of LawfulMonadAttach over the existing
attachment data. Strong lawfulness already identifies the return predicate; weak lawfulness
alone permits coarse predicates such as MonadAttach.trivial. Universal safety reasoning
needs only the strong upstream laws. Exact composition is needed for the equations below
and for introducing existential reachability through a bind.
With exact composition, AllOutputs induces the demonic ordered monad algebra MAlgOrdered m Prop,
identifying "always" with the trivial-precondition Hoare triple
(triple_top_iff_allOutputs); SomeOutput gives the angelic companion. Both
algebras are named definitions rather than global instances: transformer
algebras such as MAlgOrdered.instOptionT give failures a different meaning,
so a generic global support instance would be incoherent with them.
Scope #
Support is a value-level notion here: CanReturn does not retain the initial state or
state changes. Concretely, StateT σ m and ReaderT ρ m do
have MonadAttach instances — quantifying existentially over the initial state — and those
supports are canonical, so the elimination theory applies. They do not admit a general
ExactMonadAttach instance: flattened premises may choose unrelated indices on the sides of a
bind. For StateT this can let the continuation observe a state the prefix did not
produce; for ReaderT it can let the two premises use different environments. Reason about
those per run instead, via
mem_support_stateT_iff / mem_support_readerT_iff; PolyFunTest pins the failure with a
counterexample. Oracle- and state-relative supports belong at the specification layer
(PolyFun.PFunctor.Free.WP), which indexes the notion by a per-operation answer assignment.
A second limitation is inherited from MonadAttach: an instance must supply attach.
Informative computable attachment is not generally available for continuation-passing
encodings. Core therefore uses trivial, weakly lawful attachment for StateCpsT and
ExceptCpsT; those instances do not supply the strong laws required here.
Companion modules #
This file keys everything on pure and bind. The instances (Except, SetM, the
transformers) and the lift transport are in Support/Instances.lean; the per-run support of
StateT / ReaderT (mem_support_stateT_iff, mem_support_readerT_iff, supportFrom,
supportAt) in Support/Indexed.lean; the remaining do-fragment constructs (<*, *>,
if, match on Option / Sum) in Support/Structural.lean; loop rules in
Support/Loops.lean; and the interpretations as core WPMonads in Support/WP.lean.
Exact pure and bind composition for the existing lawful attachment predicate.
The two introduction rules complement core's elimination rules to give support equations.
This class carries proofs only: LawfulMonadAttach already fixes the return predicate,
while these additional laws need not hold for every lawful monad.
- canReturn_map_imp {α : Type u} {P : α → Prop} {x : m (Subtype P)} {a : α} : MonadAttach.CanReturn (Subtype.val <$> x) a → P a
A pure computation can return its own value.
- canReturn_bind {α β : Type u} {x : m α} {f : α → m β} {a : α} {b : β} : MonadAttach.CanReturn x a → MonadAttach.CanReturn (f a) b → MonadAttach.CanReturn (x >>= f) b
Possible outputs compose along
bind.
Instances
The set of possible outputs of a monadic computation: the Set-valued view of
CanReturn. Available for any MonadAttach; the structural equations are gated behind
ExactMonadAttach.
Instances For
Membership in support is CanReturn, definitionally.
Stated as a simp lemma because Set.ofPred and Set.Mem are implicit_reducible: rfl
and exact see through them, but simp's reducible-transparency discharge does not.
Automation contract #
Two normal forms, one per layer, chosen so the two do not fight:
- Membership normalizes to
CanReturn.mem_supportis@[simp, grind =], soa ∈ support xbecomes the atomic reachability predicate, and the per-monadcanReturn_ifflemmas take it the rest of the way to a concrete equation. Those lemmas key onCanReturn, not on membership, precisely so that they extend the chain rather than racemem_supportfor the same left-hand side. - Set-level laws produce
supportterms.support_pure,support_bind,support_mapare@[simp], so a structural goal is first decomposed at the set level and only then unfolded pointwise.
grind tags go on the directed, single-variable bridges only: membership
unfoldings and the closed-form supports of pure/none/error. The
characterizations that quantify over the support — support_eq_empty_iff,
support_nonempty_iff, and the AllOutputs/SomeOutput iffs — stay @[simp]-only.
Those are saturation hazards: grind case-splits the iff, Skolemizes the support
quantifier into a fresh witness, and the always-tagged bind expansions turn that
witness back into more support terms with no finite grounding. A proof that needs
one re-supplies it locally, as grind [allOutputs_iff_forall_support].
Emptiness and branching #
These need no exactness: they are about the shape of the Set, not about how the
monad's operations act on it.
Continuations agreeing on possible returns produce the same computation.
Continuations agreeing on the support produce the same computation.
The structural laws on CanReturn #
The same three equations keyed on the reachability predicate rather than on set
membership. support_pure/support_bind/support_map decompose a goal at the set
level; these finish it pointwise, and they are what makes CanReturn a usable normal
form rather than a dead end. Core supplies only the elimination halves
(eq_of_canReturn_pure, canReturn_bind_imp', canReturn_map_imp'); the
introduction halves are exactly ExactMonadAttach's two fields, so the equivalences
need both classes.
Their orientation matches the corresponding mem_support_* lemmas, so the two routes
through the simp set — unfold membership first, or rewrite the set first — converge on
the same normal form instead of racing.
Always / some / never output judgments #
Every possible output of x satisfies p — the "always true" judgment.
Stated over CanReturn rather than as a bounded quantifier over support, because the
modality does not need to expose a Set in its interface. This makes the same shape easy
to index when a flattened set of values is too coarse, as for StateT; the indexed
carrier there is still the honest set of value/final-state pairs. The two unindexed
spellings are definitionally interchangeable — allOutputs_iff_forall_support and
allOutputs_iff_forall_canReturn are both Iff.rfl — so nothing downstream has to choose.
Instances For
Some possible output of x satisfies p. The angelic half of the pair; see
AllOutputs for why it is stated over CanReturn.
Instances For
No possible output of x satisfies p — the "never true" judgment.
Instances For
Every possible output of x satisfies p — the "always true" judgment.
Stated over CanReturn rather than as a bounded quantifier over support, because the
modality does not need to expose a Set in its interface. This makes the same shape easy
to index when a flattened set of values is too coarse, as for StateT; the indexed
carrier there is still the honest set of value/final-state pairs. The two unindexed
spellings are definitionally interchangeable — allOutputs_iff_forall_support and
allOutputs_iff_forall_canReturn are both Iff.rfl — so nothing downstream has to choose.
Instances For
Some possible output of x satisfies p. The angelic half of the pair; see
AllOutputs for why it is stated over CanReturn.
Instances For
No possible output of x satisfies p — the "never true" judgment.
Instances For
support is the equality instance of the angelic judgment: a value is a possible
output exactly when "some output equals it" holds.
The two presentations of the layer meet here. support is the Set-valued carrier and
AllOutputs/SomeOutput are the demonic and angelic modalities over it; this equation
says nothing is lost by reading the carrier off the modality instead. The modality is the
more flexible presentation: its shape can be indexed when a flattened set of values is
too coarse, as in the StateT section below. It is also what the weakest-precondition
bridge consumes: MonadAttach.toWP is built from AllOutputs, not from support.
The angelic judgment at an equality predicate is membership in the support. The
pointwise form of support_eq_setOf_someOutput.
Negation #
Classically, the pair is dual: the demonic judgment is the negation of the angelic one at the negated predicate, and conversely. Only one direction was available before.
Disjunction, conjunction, and monotonicity in the computation #
allOutputs_and was already available; these complete the square. Note the
directions: the demonic judgment distributes over ∧ and only absorbs ∨, and the
angelic one is the mirror image.
Monotonicity in the computation, not the predicate: a demonic obligation
transfers to anything with a smaller support. allOutputs_mono varies only the
predicate.
Mapping the returned value pulls an output assertion back along that map.
Induced ordered monad algebras on Prop #
AllOutputs is the demonic Prop-carrier ordered monad algebra: its induced
MAlgOrdered.wp is the support-based weakest precondition, and the trivial-precondition
triple is exactly the "always" judgment. The angelic companion built from SomeOutput is
provided as a plain definition rather than an instance, since the two share an instance
head.
The demonic Prop-carrier ordered monad algebra of a monad with exact support:
μ asserts that every possible output is a true proposition. This is deliberately
not a global instance: for example, the existing OptionT algebra interprets
none as ⊥, whereas exact-support partial correctness interprets its empty
support vacuously. Install this definition locally when support semantics is
intended.
Instances For
Support-based characterization of the demonic Prop-valued weakest precondition.
The demonic Prop-valued weakest precondition is the "always" judgment.
The trivial-precondition Prop-valued triple is exactly the "always" judgment:
⊤-precondition triples assert that every possible output satisfies post.
The trivial-precondition Prop-valued triple against a negated postcondition is
exactly the "never" judgment.
The angelic Prop-carrier ordered monad algebra: μ asserts that some possible output
is a true proposition. Not an instance — it shares an instance head with the demonic
mAlgOrderedPropDemonic.
Instances For
Support-based characterization of the angelic Prop-valued weakest precondition —
the mirror of wp_iff_forall_support.
The angelic Prop-valued weakest precondition is the "sometimes" judgment.
The trivial-precondition angelic triple is exactly the "sometimes" judgment — the
mirror of triple_top_iff_allOutputs.
Against a negated postcondition the angelic triple says that the demonic guarantee does not hold.