Prerequisites. Direct starred prerequisites: Chapter 42. No later core chapter depends on this route.
The type 𝐹𝖭𝖺𝗍 says that a computation may perform effects and return a natural number. It does not distinguish a state computation that increments its cell from one that resets the cell, nor an exception that is impossible from one that is inevitable. A specification must relate the initial state, the returned value, and every terminal outcome. An effect name does not contain that relation.
Postconditions determine preconditions
For a result type 𝐴, a postcondition is a predicate 𝑝:𝐴→𝖴. A total pure computation returning 𝑎:𝐴 satisfies 𝑝 exactly when 𝑝(𝑎) holds. This forces the first predicate transformer.
The pure, exception, state, and state-with-exception predicate-transformer types are 𝖶𝖯𝖯𝗎𝗋𝖾(𝐴):=(𝐴→𝖴)→𝖴,𝖶𝖯𝖤𝗑𝗇(𝐴):=(𝐴→𝖴)→(𝐸→𝖴)→𝖴,𝖶𝖯𝖲𝗍(𝐴):=((𝐴×𝑆)→𝖴)→𝑆→𝖴,𝖶𝖯𝖲𝗍𝖤𝗑𝗇(𝐴):=((𝐴×𝑆)→𝖴)→(𝐸×𝑆→𝖴)→𝑆→𝖴. A transformer 𝑤 is monotone when 𝑝⇒𝑞 entails 𝑤(𝑝)⇒𝑤(𝑞), with one implication for each postcondition argument. It is conjunctive when it maps an arbitrary pointwise conjunction of postconditions to the conjunction of their preconditions.
For pure return and sequencing, the types force 𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉(𝑎):=𝜆𝑝.𝑝(𝑎),𝖻𝗂𝗇𝖽𝗐𝗉(𝑤,𝑘):=𝜆𝑝.𝑤(𝜆𝑎.𝑘(𝑎)(𝑝)).(𝑅)(𝐵) The calculation for two returns is 𝖻𝗂𝗇𝖽𝗐𝗉(𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉(𝑎),𝜆𝑥.𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉(𝑓(𝑥)))(𝑝)(𝐵)=𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉(𝑎)(𝜆𝑥.𝑝(𝑓(𝑥)))(𝑅)=𝑝(𝑓(𝑎)). The two tags name the displayed equations, not rules; the typing rules that carry the same names are introduced in definition 104.5. A postcondition alone is not a precondition until a computation determines how the two are connected.
For state, return and bind are 𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉𝖲𝗍(𝑎)(𝑝)(𝑠):=𝑝(𝑎,𝑠),𝖻𝗂𝗇𝖽𝗐𝗉𝖲𝗍(𝑤,𝑘)(𝑝)(𝑠0):=𝑤(𝜆(𝑎,𝑠1).𝑘(𝑎)(𝑝)(𝑠1))(𝑠0),𝗀𝖾𝗍𝗐𝗉(𝑝)(𝑠):=𝑝(𝑠,𝑠),𝗉𝗎𝗍𝗐𝗉(𝑠′)(𝑝)(𝑠):=𝑝(𝗎𝗇𝗂𝗍,𝑠′). For exceptions, 𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉𝖤𝗑𝗇(𝑎)(𝑝)(𝑞):=𝑝(𝑎),𝗋𝖺𝗂𝗌𝖾𝗐𝗉(𝑒)(𝑝)(𝑞):=𝑞(𝑒),𝖻𝗂𝗇𝖽𝗐𝗉𝖤𝗑𝗇(𝑤,𝑘)(𝑝)(𝑞):=𝑤(𝜆𝑎.𝑘(𝑎)(𝑝)(𝑞))(𝑞). For state with exceptions, the combined clauses are 𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉𝖲𝗍𝖤𝗑𝗇(𝑎)(𝑝)(𝑞)(𝑠):=𝑝(𝑎,𝑠),𝗋𝖺𝗂𝗌𝖾𝗐𝗉𝖲𝗍𝖤𝗑𝗇(𝑒)(𝑝)(𝑞)(𝑠):=𝑞(𝑒,𝑠),𝖻𝗂𝗇𝖽𝗐𝗉𝖲𝗍𝖤𝗑𝗇(𝑤,𝑘)(𝑝)(𝑞)(𝑠0):=𝑤(𝜆(𝑎,𝑠1).𝑘(𝑎)(𝑝)(𝑞)(𝑠1))(𝑞)(𝑠0),𝖼𝖺𝗍𝖼𝗁𝗐𝗉𝖲𝗍𝖤𝗑𝗇(𝑤,ℎ)(𝑝)(𝑞)(𝑠0):=𝑤(𝑝)(𝜆(𝑒,𝑠1).ℎ(𝑒)(𝑝)(𝑞)(𝑠1))(𝑠0). These clauses thread the state through both successful and exceptional postconditions. A semantics that discards the state on an exception is a different transformer and must replace them.
For 𝗂𝗇𝖼𝗋=𝗀𝖾𝗍()𝗍𝗈𝑥𝗂𝗇𝗉𝗎𝗍(𝑥+1), the state calculation is 𝗐𝗉(𝗂𝗇𝖼𝗋)(𝑝)(𝑠0)𝑑𝑒𝑓𝑖𝑛𝑖𝑡𝑖𝑜𝑛104.2,𝑏𝑖𝑛𝑑𝑎𝑛𝑑𝑔𝑒𝑡=𝗐𝗉(𝗉𝗎𝗍(𝑠0+1))(𝑝)(𝑠0)𝑑𝑒𝑓𝑖𝑛𝑖𝑡𝑖𝑜𝑛104.2,𝑝𝑢𝑡=𝑝(𝗎𝗇𝗂𝗍,𝑠0+1). The first step uses two clauses at once: 𝖻𝗂𝗇𝖽𝖲𝗍 hands 𝗀𝖾𝗍𝗐𝗉 the continuation 𝜆(𝑎,𝑠1).𝗉𝗎𝗍𝗐𝗉(𝑎+1)(𝑝)(𝑠1), and 𝗀𝖾𝗍𝗐𝗉 applies it to (𝑠0,𝑠0). Thus the desired contract 𝜆𝑝𝑠0.∀𝑠1.𝑠1>𝑠0⇒𝑝(𝗎𝗇𝗂𝗍,𝑠1) is discharged by the arithmetic obligation 𝑠0+1>𝑠0.
★★☆ Compute the state-with-exception transformer of a program that reads the state, raises 𝗇𝖾𝗀𝖺𝗍𝗂𝗏𝖾 when it is below zero, and otherwise writes its successor. State the successful and exceptional postconditions and the precondition obtained in each branch.
Writing predicate transformers by hand risks choosing a return and bind that do not satisfy the monad equations. The source calculus 𝖣𝖬 instead starts with a computational monad and derives its specification by a selective continuation translation.
The 𝖣𝖬 type grammar distinguishes effect-free arrows from arrows whose codomain is in the abstract monad 𝜏: 𝐴::=𝑋∣𝑏∣𝐴→𝗇𝐴∣𝐴+𝐴∣𝐴×𝐴,𝐻::=𝐴∣𝐶,𝐶::=𝐻→𝜏𝐴∣𝐻→𝗇𝐶∣𝐶×𝐶. Terms are variables, application, typed abstraction, constants, pairs and projections, injections and case analysis, together with 𝗋𝖾𝗍𝗎𝗋𝗇𝜏𝑒and𝖻𝗂𝗇𝖽𝜏𝑒1𝗍𝗈𝑥𝗂𝗇𝑒2. The judgments Δ∣Γ⊢𝑒:𝐻!𝗇andΔ∣Γ⊢𝑒:𝐴!𝜏 separate pure terms from monadic terms. A monadic term can enter a larger term only as the first premise of bind. The translation (−)⋆ is homomorphic except at 𝜏-arrows: (𝐻→𝜏𝐴)⋆:=𝐻⋆→(𝐴⋆→𝖴)→𝖴. Its computational clauses are (𝗋𝖾𝗍𝗎𝗋𝗇𝜏𝑒)⋆=𝜆𝑝.𝑝(𝑒⋆),(𝖻𝗂𝗇𝖽𝜏𝑒1𝗍𝗈𝑥𝗂𝗇𝑒2)⋆=𝜆𝑝.𝑒⋆1(𝜆𝑥.𝑒⋆2(𝑝)). The translation is indexed by a typing derivation because the same surface arrow has different behavior at an effect-free or 𝜏 codomain.
For the state monad 𝖲𝗍(𝐴)=𝑆→𝜏(𝐴×𝑆), the translated type is 𝖲𝗍(𝐴)⋆=𝑆→((𝐴×𝑆)→𝖴)→𝖴, which is 𝖶𝖯𝖲𝗍(𝐴) up to argument order. Translating the monadic state return and bind gives exactly the clauses of definition 104.2.
Proof of Theorem 104.4 — CPS produces a Dijkstra monad
Proof. All three claims are proved by induction on the displayed 𝖣𝖬 typing derivation. We give the two monadic cases first, since every other case is homomorphic.
For return, the induction hypothesis gives Δ⋆∣Γ⋆⊢𝑒⋆:𝐴⋆. Hence 𝜆𝑝.𝑝(𝑒⋆):(𝐴⋆→𝖴)→𝖴. If 𝑝⇒𝑞, application gives 𝑝(𝑒⋆)⇒𝑞(𝑒⋆). For a family (𝑝𝑖)𝑖∈𝐼, beta reduction gives (𝗋𝖾𝗍𝗎𝗋𝗇𝜏𝑒)⋆(𝜆𝑎.∀𝑖∈𝐼.𝑝𝑖(𝑎))≡∀𝑖∈𝐼.𝑝𝑖(𝑒⋆), which is the required conjunction.
For bind, inversion gives 𝑒1:𝐴!𝜏 and 𝑒2:𝐴′!𝜏 under 𝑥:𝐴. By the induction hypotheses, 𝑒⋆1 and every substituted 𝑒⋆2[𝑎/𝑥] have the indicated predicate-transformer types. Thus 𝜆𝑝.𝑒⋆1(𝜆𝑎.𝑒⋆2[𝑎/𝑥](𝑝)):(𝐴′⋆→𝖴)→𝖴. Given 𝑝⇒𝑞, monotonicity of each 𝑒⋆2[𝑎/𝑥] gives 𝑒⋆2[𝑎/𝑥](𝑝)⇒𝑒⋆2[𝑎/𝑥](𝑞); monotonicity of 𝑒⋆1 transports that pointwise implication. For conjunctions, conjunctivity of 𝑒⋆2[𝑎/𝑥] rewrites the continuation pointwise, and conjunctivity of 𝑒⋆1 then distributes the outer transformer. This proves typing, monotonicity, and conjunctivity in the bind case.
For a variable or constant, the translated term has its translated declared type. Abstraction extends Γ⋆ by the translated domain and uses the induction hypothesis on the body; application eliminates that arrow. Pairing, projections, injections, and case analysis apply their ordinary typing rules to the induction hypotheses. Their translated predicate transformers use postconditions only through translated subterms, so the preceding pointwise monotonicity and conjunction arguments apply component by component. These constructors and the two monadic forms exhaust the grammar in definition 104.3, completing claims 1 and 2.
For claim 3, induct on an equational derivation. Congruence cases follow from the induction hypotheses and congruence of the target theory. The beta, projection, and case equations translate to the corresponding target equations because the translation is homomorphic on those constructors. The return and bind equations translate by beta reduction. In particular, for all 𝑤,𝑘,ℎ and postconditions 𝑝, 𝖻𝗂𝗇𝖽𝗐𝗉(𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉(𝑎),𝑘)(𝑝)≡𝑘(𝑎)(𝑝),𝖻𝗂𝗇𝖽𝗐𝗉(𝑤,𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉)(𝑝)≡𝑤(𝑝),𝖻𝗂𝗇𝖽𝗐𝗉(𝖻𝗂𝗇𝖽𝗐𝗉(𝑤,𝑘),ℎ)(𝑝)≡𝖻𝗂𝗇𝖽𝗐𝗉(𝑤,𝜆𝑎.𝖻𝗂𝗇𝖽𝗐𝗉(𝑘(𝑎),ℎ))(𝑝). The first two equations are beta reduction. Expanding both sides of the third gives 𝑤(𝜆𝑎.𝑘(𝑎)(𝜆𝑏.ℎ(𝑏)(𝑝))) on each side. Function extensionality in the target converts these pointwise equations into the three monad laws for 𝑇⋆. No constructor outside the displayed grammar occurs in the induction. ◻
For example, target left identity is the annotated calculation 𝖻𝗂𝗇𝖽𝗐𝗉(𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉(𝑎),𝑘)(𝑝)(𝐵)=𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉(𝑎)(𝜆𝑥.𝑘(𝑥)(𝑝))(𝑅)=𝑘(𝑎)(𝑝). Monotonicity is not decoration, and one transformer at result type 𝐴=𝖭𝖺𝗍 shows what it excludes. Take 𝑤¬(𝑝):=(𝑝(0)→⊥). It is antitone: from 𝑝⇒𝑞 one gets 𝑤¬(𝑞)⇒𝑤¬(𝑝), the implication running the wrong way. To exhibit the failure, choose two postconditions with 𝑝⇒𝑞 and evaluate both at 0: 𝑝(𝑎):=⊥,𝑞(𝑎):=𝟏. Then 𝑝⇒𝑞 holds, 𝑝(0) is false, and 𝑞(0) is true. Hence 𝑤¬(𝑝)=(⊥→⊥)isinhabited,𝑤¬(𝑞)=(𝟏→⊥)isnot, so 𝑤¬(𝑝)⇒𝑤¬(𝑞) fails and 𝑤¬ is not monotone. Weakening a postcondition should never strengthen the precondition it demands, and here it does. Clause 2 of theorem 104.4 is exactly what keeps 𝑤¬ out of the generated interface: no source computation translates to it.
The type 𝖬𝐴𝑤 classifies computations returning 𝐴 whose generated weakest precondition is at least as weak as the user transformer 𝑤. Subtyping is contravariant in preconditions:
Γ⊢𝑀:𝖬𝐴𝑤Γ⊢∀𝑝.𝑤′(𝑝)⇒𝑤(𝑝)
Γ⊢𝑀:𝖬𝐴𝑤′
WP-Sub
Return and bind use the generated operations:
Γ⊢𝑉:𝐴
Γ⊢𝗋𝖾𝗍𝗎𝗋𝗇𝑉:𝖬𝐴(𝗋𝖾𝗍𝗎𝗋𝗇𝗐𝗉𝑉)
WP-Return
Γ⊢𝑀:𝖬𝐴𝑤Γ,𝑥:𝐴⊢𝑁:𝖬𝐵𝑘(𝑥)
Γ⊢𝑀𝗍𝗈𝑥𝗂𝗇𝑁:𝖬𝐵(𝖻𝗂𝗇𝖽𝗐𝗉(𝑤,𝑘))
WP-Bind
The logical implication in WP-Sub is the generated verification condition: proving it checks the advertised contract.
Three further computation types are needed to state the soundness theorem, and they are not interchangeable. 𝖳𝗈𝗍𝐴 classifies total pure terms of type 𝐴, with no specification attached. 𝖯𝗎𝗋𝖾𝐴𝑤 is the primitive instance of 𝖬𝐴𝑤 at the pure monad, whose transformer type is 𝖶𝖯𝖯𝗎𝗋𝖾(𝐴). 𝖲𝖳𝐴𝑤 is the state instance, whose representation is the state-passing pure computation 𝖲𝖳.𝗋𝖾𝗉𝗋𝐴𝑤:=Π𝑠0:𝑆.𝖯𝗎𝗋𝖾(𝐴×𝑆)(𝜆𝑝.𝑤(𝑝)(𝑠0)). Two coercions connect them. The reification map, defined after the typing rules, sends 𝑒:𝖲𝖳𝐴𝑤 to 𝗋𝖾𝗂𝖿𝗒𝑒:𝖳𝗈𝗍(𝖲𝖳.𝗋𝖾𝗉𝗋𝐴𝑤). Running drops a specification, and it may do so only when that specification is satisfiable:
Γ⊢𝑒:𝖯𝗎𝗋𝖾𝐴𝑤Γ⊢∃𝑝.𝑤(𝑝)
Γ⊢𝗋𝗎𝗇𝑒:𝖳𝗈𝗍𝐴
WP-Run
Its operational root is
𝗋𝗎𝗇(𝖯𝗎𝗋𝖾.𝗋𝖾𝗍𝗎𝗋𝗇𝑣)⟶𝑣
R-Run
The compatible closure reduces the argument of 𝗋𝗎𝗇 before this root fires. The second premise is what makes 𝖳𝗈𝗍 honest: it is unconditionally total, so a computation may enter it only after its precondition has been shown inhabited.
Apply WP-Sub to 𝗂𝗇𝖼𝗋. Its inferred transformer is 𝑤𝑖(𝑝)(𝑠0)=𝑝(𝗎𝗇𝗂𝗍,𝑠0+1). For the advertised transformer 𝑤𝑎(𝑝)(𝑠0)=∀𝑠1.𝑠1>𝑠0⇒𝑝(𝗎𝗇𝗂𝗍,𝑠1), the verification condition is ∀𝑝,𝑠0.𝑤𝑎(𝑝)(𝑠0)⇒𝑝(𝗎𝗇𝗂𝗍,𝑠0+1), proved by instantiating 𝑠1=𝑠0+1 and arithmetic. The solver may discharge that final formula, but the translation and soundness theorem determine why it is the right formula.
For a user-defined monad 𝑇 with pure implementation ̂𝑇, 𝗋𝖾𝗂𝖿𝗒𝑀 reveals the implementation of a terminating 𝑇-computation as a total term. Reflection packages such a term back at the abstract effect. Reification reduces return and bind by the corresponding operations of ̂𝑇. It is not a rule for exposing primitive concurrency, divergence, or an arbitrary handler.
Fix an EMF⋆ signature accepted by the source well-formed signature judgment. Assume the following metatheoretic package for that signature.
The target CIC is strongly normalizing, and the source’s erasure from EMF⋆ to CIC is type preserving and a strict forward simulation: every source step is matched by one or more target steps.
Source reduction preserves computation types.
If a closed normal term has type 𝖯𝗎𝗋𝖾𝐴𝑤, it is 𝖯𝗎𝗋𝖾.𝗋𝖾𝗍𝗎𝗋𝗇𝑣 for some value 𝑣:𝐴, and inversion of its computation type gives ∀𝑞:𝐴→𝖴.𝑤(𝑞)⇒𝑞(𝑣).
If ⋅⊢𝑒:𝖯𝗎𝗋𝖾𝐴𝑤,⋅⊢𝑝:𝐴→𝖴,𝑤(𝑝), then 𝗋𝗎𝗇𝑒⟶∗𝑣 for a value 𝑣:𝐴 satisfying 𝑝(𝑣); the premise 𝑤(𝑝) also discharges the satisfiability side condition of WP-Run. For state, if 𝑒:𝖲𝖳𝐴𝑤, 𝑠0:𝑆, and 𝑤(𝑝)(𝑠0), then 𝗋𝗎𝗇((𝗋𝖾𝗂𝖿𝗒𝑒)𝑠0) reduces to a pair (𝑣,𝑠1):𝐴×𝑆 satisfying 𝑝(𝑣,𝑠1).
Proof of Theorem 104.7 — Conditional WP soundness for total computations
Proof. Suppose an infinite source reduction began at 𝑒. Strict forward simulation would concatenate its nonempty target segments into an infinite CIC reduction, contradicting clause 1. Hence 𝑒 has a normal form 𝑛. Clause 2 gives ⋅⊢𝑛:𝖯𝗎𝗋𝖾𝐴𝑤. By clause 3, choose 𝑣:𝐴 such that 𝑛=𝖯𝗎𝗋𝖾.𝗋𝖾𝗍𝗎𝗋𝗇𝑣,∀𝑞:𝐴→𝖴.𝑤(𝑞)⇒𝑞(𝑣). Specialize the second formula to 𝑝 and use the premise 𝑤(𝑝); this gives 𝑝(𝑣). The source rule R-Run removes the normal-form return, so 𝗋𝗎𝗇𝑒⟶∗𝑣.
For the state clause, the displayed state representation and reification rule give (𝗋𝖾𝗂𝖿𝗒𝑒)𝑠0:𝖯𝗎𝗋𝖾(𝐴×𝑆)(𝜆𝑝.𝑤(𝑝)(𝑠0)). Apply the pure clause with postcondition 𝑝:(𝐴×𝑆)→𝖴 and premise 𝑤(𝑝)(𝑠0). The resulting value is a pair (𝑣,𝑠1):𝐴×𝑆, and 𝑝(𝑣,𝑠1) holds. ◻
The passage from a closed normal form to a returned value uses clause 3 above. Consequently, the theorem records forward simulation and the canonical-return property as hypotheses rather than deriving them from the transformer laws.
Removing totality invalidates the step from normalization to a returned value. Removing monotonicity invalidates sequencing under a stronger postcondition. Removing the well-formed signature condition permits a claimed action whose implementation and transformer disagree. The theorem does not cover arbitrary handlers, general recursion, concurrency, or an external solver’s soundness.
★★☆ Give a contract for a state computation that increments twice. Derive its transformer by two uses of WP-Bind, then write and prove the exact verification condition required by WP-Sub.
★★★ Define two state-with-exception semantics, one preserving the state on failure and one rolling it back. Calculate one program on both. Show that their transformers disagree on a postcondition that inspects the exceptional state, so no theorem transfers without a rule delta.
★★★Practical project.dijkstra-vc-generator Implement in Kappa a predicate-transformer interpreter for return, bind, get, put, raise, and catch over finite integer states. Maintain monotonicity by constructing transformers only from the displayed clauses. The named acceptance cases are 𝚒𝚗𝚌𝚛𝚎𝚖𝚎𝚗𝚝↦𝚟𝚌𝚟𝚊𝚕𝚒𝚍,𝚗𝚎𝚐𝚊𝚝𝚒𝚟𝚎-𝚛𝚊𝚒𝚜𝚎↦𝚎𝚡𝚌𝚎𝚙𝚝𝚒𝚘𝚗𝚙𝚘𝚜𝚝𝚟𝚊𝚕𝚒𝚍,𝚋𝚊𝚍-𝚊𝚍𝚟𝚎𝚛𝚝𝚒𝚜𝚎𝚍-𝚙𝚘𝚜𝚝↦𝚟𝚌𝚛𝚎𝚓𝚎𝚌𝚝𝚎𝚍. Require also the exact lines 𝚋𝚒𝚗𝚍𝚝𝚑𝚛𝚎𝚊𝚍𝚜𝚞𝚙𝚍𝚊𝚝𝚎𝚍𝚜𝚝𝚊𝚝𝚎,𝚌𝚊𝚝𝚌𝚑𝚑𝚊𝚗𝚍𝚕𝚎𝚜𝚝𝚑𝚎𝚛𝚊𝚒𝚜𝚎𝚍𝚌𝚘𝚖𝚙𝚞𝚝𝚊𝚝𝚒𝚘𝚗,𝚙𝚞𝚝𝚌𝚑𝚊𝚗𝚐𝚎𝚜𝚝𝚑𝚎𝚜𝚝𝚊𝚝𝚎𝚜𝚎𝚎𝚗𝚋𝚢𝚐𝚎𝚝. A mutation that resumes the bind body in the original state must fail increment vc valid and the first and third lines above while the checker itself remains well typed. Explain which occurrence of the intermediate state in 𝖻𝗂𝗇𝖽𝗐𝗉𝖲𝗍 each failure exercises. The program decides finite verification conditions; it does not prove theorem 104.7 or justify an SMT solver.
Sources. The 𝖣𝖬 grammar, selective CPS, generated Dijkstra monads, EMF⋆ calculus, and total-correctness result follow Ahman et al. [AHM^+17]. The dependent-CBPV interface of chapter 42 motivates sequencing, but no theorem there is used as a substitute for the predicate-transformer proof. The selective-CPS proof in Appendix A.2 occupies printed pp. 19–21, so its induction is incorporated in theorem 104.4 rather than imported.