EconCSLib.Foundation.CostM #
CostM C A is the writer monad over an arbitrary additive monoid C: a value of
type A together with an accumulated cost in C. Sequential composition
(>>=) adds the cost components via the monoid operation.
Design #
The cost type C is deliberately abstract. Picking C is what selects which
resource is being measured; the monad itself is the same for all of them.
C := ℕ— count comparisons, recursive calls, or any single additive resource. This is the conventional "time complexity" instance.C := A × B— track two costs at once.Prod.instAddMonoidmakes this automatic; no extra wiring needed.C := Finset Awith+ := (· ∪ ·),0 := ∅— track the set of subproblems touched by a recursive algorithm. The monoid is idempotent (s + s = s); this is the algebraic shadow of memoization.C := { c : ℕ × ℤ // c.2 ≤ c.1 }(peak/delta) — a non-additive monoid for peak memory. This is the only resource shape that additive cost fundamentally cannot express.
The structure field is cost, not time: time is one cost among many and
has no architectural privilege.
This is the standard writer-monad-over-a-monoid pattern; see Danielsson, Lightweight Semiformal Time Complexity Analysis for Purely Functional Data Structures (POPL 2008).
Discipline #
Cost annotations are trusted: the elaborator does not check that tick
calls correspond to actual work. Each algorithm file must document its cost
model — what counts as a unit, what is free, whether recursive calls are
charged — and the author must tick accordingly.
Proofs separate cleanly:
The two are independent: changing the cost model never disturbs correctness proofs, and refactoring the algorithm preserves cost annotations by construction.
Main definitions #
CostM C A— the monad.CostM.pure,CostM.bind,CostM.tick— primitives.Monad (CostM C)for[Zero C] [Add C].LawfulMonad (CostM C)for[AddMonoid C].CostM.par— independent (parallel) composition; cost combined via⊔. Requires[SemilatticeSup C].CostM.Bounded/CostM.IsPolyBounded— bound predicates over[LE C](and theC = ℕpolynomial specialization).
Notation #
tick c— charge costc.✓[c] body— sugar fordo tick c; bodyinside ado-block.✓ body—✓[1] body; requires[OfNat C 1]at the use site.- implicit coercion
(m : A)— aCoe (CostM C A) Ainstance projectsm.ret. Use the.costfield explicitly to get the cost.
Attribution #
The monad core of this file is adapted from TimeM in the
leanprover/cslib project,
file Cslib/Algorithms/Lean/TimeM.lean (Apache License 2.0).
Original authors of TimeM: Sorrachai Yingchareonthawornhcai and
Eric Wieser. Original copyright © 2025 Sorrachai Yingchareonthawornhcai.
What is adapted from upstream #
- the
structure CostMlayout (ret, accumulated-cost field) ↔TimeM(ret,time); protected def pure/protected def bindand the matchinginstance : Pure / Bind / Functor / Seqtypeclass instances;instance : Monadandinstance : LawfulMonad;- the
simp-lemma layout forret_*/cost_*projections; def tick, the✓[c] bodyand✓ bodydoElem macros;- the writer-over-additive-monoid design pattern and the Danielsson POPL 2008 reference.
What is added in this file (not upstream as of the pinned commit) #
- Generalization of vocabulary from "time" to "cost"; the field is
cost, the type parameter isC. This makes room for cost shapes that are not time — set-of-cells-touched (Visited, idempotent monoid), peak / delta memory (Cells, tropical monoid), product costs (work × depth), etc. def par(binary parallel composition) anddef parList(n-ary parallel composition overList), with cost combined via the semilattice join⊔; correspondingsimplemmas.- The complexity-bound predicates
BoundedandIsPolyBoundedover[LE C]andC = ℕrespectively. instance instCoeHead : CoeHead (CostM C A) Aprojectingm.ret, giving(m : A)shorthand at use sites.- Companion modules
Foundation/CostM/Cells.lean(tropical / peak memory) andFoundation/CostM/Visited.lean(idempotent / footprint).
Writer monad over an arbitrary additive monoid C.
A CostM C A is a return value of type A together with an accumulated cost
in C. The cost field aggregates via + and 0 of C. See the file
docstring for the design rationale and for the choices of C that recover
specific complexity measures.
- ret : A
The result of the computation.
- cost : C
The accumulated cost in
C.
Instances For
Equations
- CostM.instPure = { pure := fun {α : Type ?u.13} => CostM.pure }
Equations
- CostM.instBind = { bind := fun {α β : Type ?u.14} => CostM.bind }
Charge a cost of c, returning unit. Use inside a do-block.
Equations
- CostM.tick c = { ret := PUnit.unit, cost := c }
Instances For
✓ body is ✓[1] body. The use site must provide [OfNat C 1].
Equations
- CostM.«doElem✓_» = Lean.ParserDescr.node `CostM.«doElem✓_» 1022 (Lean.ParserDescr.binary `andthen (Lean.ParserDescr.symbol "✓") (Lean.ParserDescr.cat `doElem 0))
Instances For
Parallel composition #
par m₁ m₂ runs two independent CostM computations side by side; the cost
is combined via ⊔ (sup). This is not a monadic operation — neither
operand's value feeds into the other. Use this only when the two sides are
data-independent.
The choice of ⊔ for the cost models the "depth" / branch-worst-case view:
both branches run concurrently, so the joint cost is the worse of the two.
For combined work-and-depth tracking (cost type ℕ × ℕ with + on work and
⊔ on depth), a custom combining function per cost type is required; this
file does not provide it.
N-ary parallel composition #
parList ms extends par from a binary combinator to an arbitrary List of
operands. All operands run independently (no data dependency between them);
returns are collected into a List; costs are combined by the sup-semilattice
join (⊔). The empty list has cost ⊥ (= 0 for C = ℕ).
parList requires [OrderBot C] in addition to [SemilatticeSup C] because
the empty list needs an identity element for ⊔.
For n independent operations each charging c_i, the joint parallel cost is
c_1 ⊔ c_2 ⊔ ⋯ ⊔ c_n (the max for C = ℕ). This corresponds to a flat
"fan-out / fan-in" depth-1 schedule with n workers.
N-ary parallel composition over a List of independent CostM
operands. Returns are paired into a list in the original order; the cost is
the sup of all operand costs (or ⊥ for the empty list).
Equations
Instances For
Complexity bound predicates #
Bounded alg size bound says "for every input i, the cost of alg i is at
most bound (size i)". The size function reduces the input to a natural
number (the conventional complexity-theoretic notion of "input size"). The
bound function maps that size to an expected cost in C.
IsPolyBounded specializes to C = ℕ and existentially quantifies over the
polynomial coefficient and degree: ∃ c k, cost ≤ c * size^k.
The cost of alg i is bounded by bound (size i) for every input i.
Equations
- CostM.Bounded alg size bound = ∀ (i : Input), (alg i).cost ≤ bound (size i)