Monad algebras #
This file contains two layers:
- A minimal
MonadAlgebrainterface: a structure mapm α → α, made Eilenberg-Moore byLawfulMonadAlgebra. - A Loom-style ordered monad algebra interface
MAlgOrderedwithwp/triple.
Public credit / attribution:
- Loom project: https://github.com/verse-lab/loom
- POPL 2026 paper: "Foundational Multi-Modal Program Verifiers", Vladimir Gladshtein, George Pîrlea, Qiyuan Zhao, Vitaly Kurin, and Ilya Sergey. DOI: https://doi.org/10.1145/3776719
The ordered monad algebra perspective (MAlgOrdered, wp, triple) in this file is adapted from
Loom's MonadAlgebras development.
A monad algebra is lawful when its structure map is compatible with the monad's pure and
bind, making it an Eilenberg-Moore algebra.
Instances
Loom-style ordered monad algebras #
Automation contract #
The @[simp] set here drives wp inwards through program structure until it meets a
leaf, mirroring the way core's monad simp set drives bind towards right-nested normal
form. Four lemmas carry it — wp_pure, wp_bind, wp_map, wp_seq — and each strictly
decreases the program argument to proper subprograms. Together they normalize the
fragment built from pure, >>=, <$>, and <*>.
Deliberately untagged: wp_mono and every Triple rule. They are not equations, and the
Triple rules are directed reasoning steps a user chooses, not normalizations. wpExc /
wpOpt keep their own leaf rules tagged but not their _def unfoldings, so goals stated
in terms of the honest two-postcondition combinators are not silently collapsed back into
the lossy ⊥-based ones.
No grind annotations. wp_bind introduces a fresh higher-order argument
(fun a => wp (f a) post) on its right-hand side, which is exactly the shape that makes
grind saturate; simp's inside-out rewriting handles it without that risk.
Ordered monad algebra interface used for quantitative WP/triple reasoning.
- μ : m l → l
The ordered algebra's structure map, collapsing
m linto a lattice elementl.
Instances
Weakest precondition induced by μ.
Instances For
Hoare-style triple induced by wp.
Instances For
wp is functorial in the program return value.
wp preserves applicative sequencing.
The rest of the do fragment #
<*, *>, if, if h :, and match on Option / Sum (through Option.elim /
Sum.elim) push wp inwards like the four rules above; each is an equation on proper
subprograms and joins the same @[simp] set.
Rule for pure computations in Triple.
Rule for map in Triple.
Monotonicity of Triple in its postcondition.
Monotonicity of Triple in its precondition.
Transformer lifting instances #
There is deliberately no globally registered base instance. The structure map
μ : m l → l is a choice of semantics, not something a monad determines: on FreeM P it
is a per-operation spec (PFunctor.OpSpec.toMAlgOrdered takes the spec and its
monotonicity proof as arguments), and on a monad with exact support it is the demonic or
the angelic reading. Nothing canonical exists to register.
What is registered are the transformer lifts below. The intended pattern is to install
the intended base algebra locally at a verification boundary and let the lifts compose
above it; PolyFunTest/Control/MonadAlgebra.lean checks that each lift, and stacks of
them, resolve by synthesis over one local base.
The carriers follow the shape of what the transformer adds: StateT, ReaderT, and
WriterT index the lattice by their state, environment, and accumulated log, while
ExceptT and OptionT keep the base carrier and collapse failure to ⊥. The collapsing
pair loses the failure branch on purpose; wpExc / wpOpt below are the honest
two-postcondition alternatives.
Lift an ordered monad algebra through StateT.
Lift an ordered monad algebra through ReaderT.
Lift an ordered monad algebra through ExceptT by interpreting exceptions as ⊥.
Lift an ordered monad algebra through OptionT by interpreting none as ⊥.
Lift an ordered monad algebra through WriterT, threading the accumulated log.
The carrier is ω → l for the same reason StateT's is σ → l: bind multiplies the
prefix's log into the continuation's, so a postcondition that mentions the log has to be
told what has already been written. Taking ω → l at the unit recovers the log-oblivious
reading, so nothing is lost by indexing.
Honest exception WP #
wpExc is a derived weakest-precondition combinator for ExceptT that records both
a success postcondition postOk : α → l and a failure postcondition postErr : ε → l,
rather than collapsing failures to ⊥. Symmetrically, wpOpt is the analogue for
OptionT.
These are derived: they use only the unary algebra MAlgOrdered m l of the underlying
monad. The standard MAlgOrdered (ExceptT ε m) / MAlgOrdered (OptionT m) lifts then
correspond to wpExc · · (fun _ => ⊥) and wpOpt · · ⊥ respectively, which is the
"lossy" case. The honest combinators come with their own pure/throw/bind/
tryCatch rules that enable side-by-side reasoning about success and failure paths.
Honest weakest precondition for ExceptT: takes a success postcondition postOk
and a failure postcondition postErr, and returns the unary wp over the underlying
monad with the postcondition split by case.
Instances For
Honest weakest precondition for OptionT: takes a some postcondition and a
none postcondition.
Instances For
throw e is ExceptT.mk (pure (Except.error e)).
Bind law for wpExc: only the success branch threads through the post-bind
continuation; the failure postcondition is preserved at every step.
Catch law for wpExc: tryCatch x h exchanges its failure postcondition for the
honest WP of the handler.
wpExc is monotone in both postconditions.