The CostM Complexity Monad
CostM C α is the writer monad over an arbitrary additive monoid C: a
return value of type α paired with an accumulated cost in C,
$$
\mathrm{CostM}\,C\,\alpha \;=\; \alpha \times C.
$$
Sequential composition (bind, written >>=) adds the cost components,
$$
\mathrm{bind}(m, f).\mathrm{cost}
\;=\; m.\mathrm{cost} \,+\, (f\,m.\mathrm{ret}).\mathrm{cost},
\qquad
(\mathrm{pure}\,a).\mathrm{cost} = 0,
$$
so a program's total cost is the monoid-sum of the costs charged along its
execution.
The cost type C is deliberately abstract: choosing C is what selects the
resource being measured, while the monad is identical for all of them.
C := ℕ(additive) — count comparisons, recursive calls, modulus operations: the conventional "time" instance.C := A × B— track two additive costs at once viaProd.instAddMonoid.C := Visited α(idempotent,s + s = s) — the set of subproblems touched; the algebraic shadow of memoization. See Memoization Footprint via the Visited Monoid.C := Cells(tropical peak/delta) — peak memory, the one resource shape additive cost fundamentally cannot express. See Peak-Memory Cost via the Cells Monoid.
Discipline: cost annotations are trusted
The elaborator does not verify that tick calls correspond to real work.
Each algorithm file documents its own cost model — what a unit is, what is
free, whether recursive calls are charged — and the author ticks
accordingly. Proofs then separate cleanly along the two structure fields:
- functional correctness lives on
m.ret(alias⟪m⟫); - complexity bounds live on
m.cost.
The two are independent: changing the cost model never disturbs a correctness proof, and refactoring the algorithm preserves cost annotations by construction. The Boyer–Moore example (Worked Example: Constant-Space Boyer–Moore Majority) proves a cost bound while deliberately not proving correctness, to exhibit the decoupling.
Lean declarations
CostM— the two-field structure (ret,cost).CostM.pure[Zero C],CostM.bind[Add C]— the monad primitives;CostM.instMonad[Zero C] [Add C]andCostM.instLawfulMonad[AddMonoid C]package them as a lawful monad.CostM.tick c : CostM C PUnitcharges costc; the✓[c] bodymacro is sugar fordo tick c; body, and✓ bodyis✓[1] body(needs[OfNat C 1]).CostM.Bounded alg size bound[LE C]says∀ i, (alg i).cost ≤ bound (size i);CostM.IsPolyBoundedspecializes toC = ℕwith∃ c k, cost ≤ c · size^k.
Parallel composition (par, parList) is documented separately in
Parallel Composition in CostM.
References
- [Danielsson 2008] Nils Anders Danielsson, Lightweight Semiformal Time Complexity Analysis for Purely Functional Data Structures, POPL 2008. The writer-monad-over-a-monoid pattern for cost analysis.
Provenance
- The monad core (
structure,Pure/Bind/Functor/Seq,Monad/LawfulMonad,tick, the✓notation) is adapted fromTimeMin leanprover/cslib,Cslib/Algorithms/Lean/TimeM.lean(Apache 2.0), by Sorrachai Yingchareonthawornhcai and Eric Wieser. EconCSLib generalizes "time" to a general cost monoidCand addspar/parList,Bounded/IsPolyBounded, and theCoeHeadprojection.
Used by
- Peak-Memory Cost via the Cells Monoid
- Worked Example: Constant-Space Boyer–Moore Majority
- Worked Example: Euclidean GCD Step Count
- Worked Example: Longest Common Subsequence DP Grid
- Worked Example: Memoized Fibonacci Footprint
- Worked Example: N-ary parList Depth Bound
- Worked Example: Balanced Parallel Sum Depth
- Worked Example: Quadratic Naive List Reversal
- Parallel Composition in CostM
- Memoization Footprint via the Visited Monoid