The Lean Language Reference

17.2. Predicate Transformers🔗

A predicate transformer semantics is an interpretation of programs as functions from predicates to predicates, rather than values to values. A postcondition is an assertion that holds after running a program, while a precondition is an assertion that must hold prior to running the program in order for the postcondition to be guaranteed to hold.

The predicate transformer semantics used by vcgen transforms postconditions into the weakest preconditions under which the program will ensure the postcondition. An assertion P is weaker than P' if, in all states, P' suffices to prove P, but P does not suffice to prove P'. Logically equivalent assertions are considered to be equal.

The predicates in question can be stateful: they can mention the program's current state. Furthermore, postconditions can relate the return value and any exceptions thrown by the program to the final state. Each program type that can be used with vcgen is assigned an assertion type and an exception postcondition type by an instance of WP. For a state monad such as StateM Nat, an assertion is a predicate on the state, of type Nat → Prop. A postcondition additionally takes the return value as its first argument, and the exception postcondition includes an assertion for each exception that the program can throw.

17.2.1. Assertion Lattices🔗

The predicate transformer semantics of programs is based on a logic in which propositions may mention the program's state. Here, “state” refers not only to mutable state, but also to read-only values such as those that are provided via ReaderT. Different program types have different assertion types, which can be any complete lattice. More specifically, assertion types instantiate Assertion, which is a Lean.Parser.Command.classAbbrev : commandExpands ``` class abbrev C <params> := D_1, ..., D_n ``` into ``` class C <params> extends D_1, ..., D_n attribute [instance] C.mk ``` class abbrev over CompleteLattice that is recognized by vcgen.

🔗type class
Std.WP.Assertion.{w} (α : Type w) : Type w
Std.WP.Assertion.{w} (α : Type w) : Type w

An assertion type is equipped with a CompleteLattice structure, used as the carrier for pre- and postconditions.

Std.WP.Assertion.mk.{w}
rel : α → α → Prop
Inherited from
  1. CompleteLattice α
rel_refl : ∀ {x : α}, x ⊑ x
Inherited from
  1. CompleteLattice α
rel_trans : ∀ {x y z : α}, x ⊑ y → y ⊑ z → x ⊑ z
Inherited from
  1. CompleteLattice α
rel_antisymm : ∀ {x y : α}, x ⊑ y → y ⊑ x → x = y
Inherited from
  1. CompleteLattice α
has_sup : ∀ (c : α → Prop), Exists (is_sup c)
Inherited from
  1. CompleteLattice α

The lattice structure provides the logical vocabulary of assertions:

  • The order relation ⊑ is entailment.

  • The meet ⊓ is conjunction and the join ⊔ is disjunction.

  • The top element ⊤ is the trivial assertion and the bottom element ⊥ is the absurd assertion.

  • The indexed supremum ⨆ is existential quantification and the indexed infimum ⨅ is universal quantification. Their notation resembles that of ordinary quantifiers; for example, ⨆ x : α, P x asserts the existence of something that satisfies P.

  • The Heyting implication ⇨ is implication internal to the assertion language.

The difference between entailment and implication is that entailment is a statement in Lean's logic, while implication is internal to the assertion language: for assertions P and Q, P ⊑ Q is a Prop while P ⇨ Q is again an assertion.

Std.WP comes with Assertion instances for Prop, Unit, function types, and pairs. The lattice operations on Prop coincide with the ordinary logical connectives, with entailment being implication. The lattice operations on a function type such as Nat → Prop operate pointwise, so entailment of state predicates is universally-quantified implication. The lattice operations on a pair such as (Nat → Prop) × (Bool → Prop) operate component-wise, so entailment of paired predicates is the conjunction of entailments of the component predicates.

Entailment at Different Assertion Types

At Prop, entailment is implication:

example (P Q : Prop) : (P ⊑ Q) = (P → Q) := rfl

At a state predicate type, entailment is implication at every state:

example (f g : Nat → Prop) : (f ⊑ g) = ∀ n, f n → g n := rfl

Entailment of pairs is conjunction of entailments:

example (f₁ f₂ : Nat → Prop) (g₁ g₂ : Bool → Prop) : ((f₁, g₁) ⊑ (f₂, g₂)) = ((f₁ ⊑ f₂) ∧ (g₁ ⊑ g₂)) := rfl

A concrete entailment between state predicates can be proved by unfolding the entailment order and reasoning about each state:

def Positive : Nat → Prop := fun n => 0 < n def AtLeastTwo : Nat → Prop := fun n => 2 ≤ n example : AtLeastTwo ⊑ Positive := ⊢ AtLeastTwo ⊑ Positive ⊢ ∀ (x : Nat), 2 ≤ x → 0 < x x:Nath:2 ≤ x⊢ 0 < x All goals completed! 🐙

Ordinary propositions that do not mention the state can be embedded into any assertion lattice with corner brackets. This is written with the syntax ⌜P⌝, which is notation for Lean.Order.CompleteLattice.ofProp.

syntaxEmbedding Propositions
term ::= ...
    | Embedding of propositions into an CompleteLattice type. `⌜p⌝` embeds `p : Prop` as `⊤` if `p` holds
and `⊥` otherwise. ⌜term⌝

Embedding of propositions into an CompleteLattice type. ⌜p⌝ embeds p : Prop as ⊤ if p holds and ⊥ otherwise.

🔗def

Embedding of propositions into an CompleteLattice type. ⌜p⌝ embeds p : Prop as ⊤ if p holds and ⊥ otherwise.

An assertion of the form ⌜p⌝ is pure: its truth is independent of the state. At the assertion type Prop, ⌜p⌝ is the proposition p itself, and at a state predicate type such as Nat → Prop, it is the constant predicate fun _ => p. Specifications for a concrete monad can therefore state propositions directly, as in fun s => s = n. Corner brackets are necessary when the assertion type is abstract, as in a specification that quantifies over a monad and its assertion type Pred. In this case, a proposition such as n = k is not an assertion, but ⌜n = k⌝ embeds it into Pred. The monad-polymorphic example at the end of this chapter uses corner brackets for this reason. vcgen moves pure preconditions into the ordinary Lean context, where they become hypotheses of the verification conditions.

Assertions for State Monads

The predicate ItIsSecret expresses that a state of type String is "secret":

def ItIsSecret : String → Prop := fun s => s = "secret"

Entailment between such assertions is pointwise implication:

example : ItIsSecret ⊑ (⌜True⌝ : String → Prop) := ⊢ ItIsSecret ⊑ ⌜True⌝ All goals completed! 🐙

17.2.2. Exception Postconditions🔗

A postcondition for successful termination is a function from the return value to an assertion. Programs that can throw exceptions additionally require an exception postcondition that asserts what holds when the program throws an exception. The exception postcondition type of a given monad is determined by its WP instance.

For example, the WP instance for ExceptT String (StateM Nat) Bool determines Nat → Prop as the assertion type, hence Bool → Nat → Prop as the postcondition type, and String → Nat → Prop as the exception postcondition type. A monad without exceptions has the exception postcondition type Unit.

For a base monad with assertion type Pred and exception postcondition type EPred, an exception layer such as ExceptT ε contributes one more exception postcondition, which gives the type (ε → Pred) × EPred.

Since unit and pair types come with Assertion instances, these “stacks” of exception postconditions are automatically assertions as well. The WP instances for monad transformers ensure that stacks of monad transformers have corresponding exception postcondition stacks.

syntaxException Postcondition Stacks

The notation EStack⟨e₁, e₂, ...⟩ abbreviates the type of exception postcondition stack e₁ × (e₂ × (... × Unit)), and the notation estack⟨v₁, v₂, ...⟩ builds a value (v₁, v₂, ..., ()) of such a stack.

term ::= ...
    | Exception postcondition stack **type**: `EStack⟨ε₁ → l, ε₂ → l⟩` is `(ε₁ → l) × (ε₂ → l) × EStack⟨⟩`. EStack⟨term,*⟩
term ::= ...
    | Exception postcondition stack **value**: `estack⟨e₁, e₂⟩` is `(e₁, e₂, ())`. estack⟨term,*⟩

Specifications for programs that might throw exceptions come in two varieties. The total correctness interpretation ⦃P⦄ prog ⦃Q'⦄ asserts that, given P holds, then prog terminates normally and Q' holds for the result. The partial correctness interpretation ⦃P⦄ prog ⦃Q'; ⊤⦄ asserts that, given P holds, and if prog terminates normally then Q' holds for the result. A triple without an explicit exception postcondition carries the bottom assertion (⊥ : EPred) and thus has the total interpretation; between ⊥ and ⊤, the exception postcondition expresses a spectrum of correctness properties.

17.2.3. Predicate Transformers🔗

A predicate transformer is a function from postconditions into assertions that describe preconditions.

🔗structure
Lean.Order.PredTrans.{u, v, w} (Pred : Type u) (EPred : Type v) (α : Type w) : Type (max (max u v) w)
Lean.Order.PredTrans.{u, v, w} (Pred : Type u) (EPred : Type v) (α : Type w) : Type (max (max u v) w)

A predicate transformer from postconditions to preconditions.

Given a return type α, a lattice Pred for assertions, and an exception assertion type EPred, PredTrans Pred EPred α wraps a function (α → Pred) → EPred → Pred.

Lean.Order.PredTrans.mk.{u, v, w}
apply : (α → Pred) → EPred → Pred

Apply the predicate transformer to a postcondition and exception postcondition.

The partial order on predicate transformers is inherited pointwise from the assertion lattice: x ⊑ y when x.apply post epost ⊑ y.apply post epost for all post and epost. Every predicate transformer that a WP instance produces is monotone: if post ⊑ post' and epost ⊑ epost', then x.apply post epost ⊑ x.apply post' epost'.

🔗def
Lean.Order.PredTrans.monotone.{u, v, w} {Pred : Type u} {EPred : Type v} {α : Type w} [PartialOrder Pred] [PartialOrder EPred] (pt : PredTrans Pred EPred α) : Prop
Lean.Order.PredTrans.monotone.{u, v, w} {Pred : Type u} {EPred : Type v} {α : Type w} [PartialOrder Pred] [PartialOrder EPred] (pt : PredTrans Pred EPred α) : Prop

Monotonicity property for a predicate transformer: if both post and epost grow, then the resulting precondition grows.

Predicate transformers form a monad. The pure operator is the identity transformer; it simply instantiates the postcondition with its argument. The bind operator composes predicate transformers.

🔗def
Lean.Order.PredTrans.pure.{u, v, w} {Pred : Type u} {EPred : Type v} {α : Type w} (a : α) : PredTrans Pred EPred α
Lean.Order.PredTrans.pure.{u, v, w} {Pred : Type u} {EPred : Type v} {α : Type w} (a : α) : PredTrans Pred EPred α

pure a applies the postcondition to a.

🔗def
Lean.Order.PredTrans.bind.{u, v, w} {Pred : Type u} {EPred : Type v} {α β : Type w} (x : PredTrans Pred EPred α) (f : α → PredTrans Pred EPred β) : PredTrans Pred EPred β
Lean.Order.PredTrans.bind.{u, v, w} {Pred : Type u} {EPred : Type v} {α β : Type w} (x : PredTrans Pred EPred α) (f : α → PredTrans Pred EPred β) : PredTrans Pred EPred β

bind x f threads the postcondition through the continuation f.

The helper operators PredTrans.pushArg, PredTrans.pushExceptT, and PredTrans.pushOptionT modify a predicate transformer by adding a standard side effect. They are used to implement the WP instances for transformers such as StateT, ExceptT, and OptionT; they can also be used to implement monads that can be thought of in terms of one of these. For example, PredTrans.pushArg is typically used for state monads, but can also be used to implement a reader monad's instance, treating the reader's value as read-only state.

🔗def
Lean.Order.PredTrans.pushArg.{u, v, w, z} {Pred : Type u} {EPred : Type v} {α : Type w} {σ : Type z} (x : σ → PredTrans Pred EPred (α × σ)) : PredTrans (σ → Pred) EPred α
Lean.Order.PredTrans.pushArg.{u, v, w, z} {Pred : Type u} {EPred : Type v} {α : Type w} {σ : Type z} (x : σ → PredTrans Pred EPred (α × σ)) : PredTrans (σ → Pred) EPred α

Adds a state argument to a predicate transformer.

Given a state-dependent transformer σ → PredTrans Pred EPred (α × σ), produces a transformer over σ → Pred that threads the state through postconditions.

🔗def
Lean.Order.PredTrans.pushExceptT.{u, v, w, z} {Pred : Type u} {EPred : Type v} {α : Type w} {ε : Type z} (x : PredTrans Pred EPred (Except ε α)) : PredTrans Pred ((ε → Pred) × EPred) α
Lean.Order.PredTrans.pushExceptT.{u, v, w, z} {Pred : Type u} {EPred : Type v} {α : Type w} {ε : Type z} (x : PredTrans Pred EPred (Except ε α)) : PredTrans Pred ((ε → Pred) × EPred) α

Adds an exception postcondition layer to a predicate transformer, mirroring ExceptT.

Given a transformer over Except ε α, produces one over α with an additional exception postcondition for ε. The normal and error postconditions are combined via pushExcept.

🔗def
Lean.Order.PredTrans.pushOptionT.{u, v, w} {Pred : Type u} {EPred : Type v} {α : Type w} (x : PredTrans Pred EPred (Option α)) : PredTrans Pred ((Unit → Pred) × EPred) α
Lean.Order.PredTrans.pushOptionT.{u, v, w} {Pred : Type u} {EPred : Type v} {α : Type w} (x : PredTrans Pred EPred (Option α)) : PredTrans Pred ((Unit → Pred) × EPred) α

Adds an early-termination layer to a predicate transformer, mirroring OptionT.

Given a transformer over Option α, produces one over α with an additional exception postcondition for the none case.

17.2.3.1. Weakest Preconditions🔗

The weakest precondition semantics of a program type is provided by the WP type class. An instance WP Prog Value Pred EPred interprets programs of type Prog with results of type Value as monotone predicate transformers over the assertion type Pred and the exception postcondition type EPred. The type Prog determines Value, Pred and EPred as outParams. The function wp applies the interpretation: wp prog post epost is the weakest precondition under which the program prog establishes the postcondition post and the exception postcondition epost.

🔗type class
Std.WP.WP.{u, v, w, w'} (Prog : Type u) (Value : outParam (Type v)) (Pred : outParam (Type w)) (EPred : outParam (Type w')) [Assertion Pred] [Assertion EPred] : Type (max (max (max u v) w) w')
Std.WP.WP.{u, v, w, w'} (Prog : Type u) (Value : outParam (Type v)) (Pred : outParam (Type w)) (EPred : outParam (Type w')) [Assertion Pred] [Assertion EPred] : Type (max (max (max u v) w) w')

Weakest precondition interpretation of a program type Prog whose results have type Value, as a monotone predicate transformer over assertion language Pred with exception postconditions EPred.

Std.WP.WP.mk.{u, v, w, w'}
wpTrans : Prog → PredTrans Pred EPred Value

The weakest precondition transformer for a program.

wp_trans_monotone : ∀ (x : Prog), (WP.wpTrans x).monotone

Monotonicity: weaker postconditions yield weaker preconditions.

🔗def
Std.WP.WP.wp.{u, v, w, w'} {Prog : Type u} {Value : Type v} {Pred : Type w} {EPred : Type w'} [Assertion Pred] [Assertion EPred] [self : WP Prog Value Pred EPred] (x : Prog) (post : Value → Pred) (epost : EPred) : Pred
Std.WP.WP.wp.{u, v, w, w'} {Prog : Type u} {Value : Type v} {Pred : Type w} {EPred : Type w'} [Assertion Pred] [Assertion EPred] [self : WP Prog Value Pred EPred] (x : Prog) (post : Value → Pred) (epost : EPred) : Pred

Weakest precondition of x for normal postcondition post and exception postcondition epost. The WP interpretation can be supplied explicitly via dot notation (inst.wp x post epost).

A program prog that is conjunctive distributes a weakest precondition of a meet of two postconditions into a meet of the weakest preconditions of the postconditions. The type class WPConjunctive captures this property, which allows two specifications for prog to be combined into a single strongest one. This is analogous to Triple.and, which combines Hoare triples.

🔗type class
Std.WP.WPConjunctive.{u, v, w, z} {Prog : Type u} {Value : outParam (Type v)} {Pred : outParam (Type w)} {EPred : outParam (Type z)} [Assertion Pred] [Assertion EPred] [WP Prog Value Pred EPred] (x : Prog) : Prop
Std.WP.WPConjunctive.{u, v, w, z} {Prog : Type u} {Value : outParam (Type v)} {Pred : outParam (Type w)} {EPred : outParam (Type z)} [Assertion Pred] [Assertion EPred] [WP Prog Value Pred EPred] (x : Prog) : Prop

wp x is sub-conjunctive: the meet of the weakest preconditions of two postconditions lies below the weakest precondition of the componentwise meet of the postconditions. A healthiness condition of the WP interpretation for the individual program x; it holds for the base interpretations and lifts through the transformers.

Std.WP.WPConjunctive.mk.{u, v, w, z}
wp_meet_wp_le : ∀ (Q₁ Q₂ : Value → Pred) (E₁ E₂ : EPred), wp x Q₁ E₁ ⊓ wp x Q₂ E₂ ⊑ wp x (Q₁ ⊓ Q₂) (E₁ ⊓ E₂)

The meet of the weakest preconditions wp x Q₁ E₁ and wp x Q₂ E₂ lies below the weakest precondition wp x (Q₁ ⊓ Q₂) (E₁ ⊓ E₂) of the componentwise meet of the postconditions.

17.2.3.2. Monads That Preserve Weakest Preconditions🔗

Most of the built-in specification lemmas for vcgen rely on the presence of a WPMonad instance. A WP instance is provided for a particular type, but a monad is a polymorphic type. StateM Nat Unit and StateM Nat String are different types, but their weakest preconditions should be related. Instances of WPMonad assign a WP interpretation to each for every type that the monad can be applied to. It additionally contains evidence that this interpretation is sound for the monad's implementations of pure and bind. This means that to prove something about a Lean.Parser.Term.do : termdo block, it suffices to prove it about the chain of elements of the block. In other words, a WPMonad instance enables compositional reasoning about Lean.Parser.Term.do : termdo blocks.

🔗type class
Std.WP.WPMonad.{u, v, w, w'} (m : Type u → Type v) (Pred : outParam (Type w)) (EPred : outParam (Type w')) [Monad m] [Assertion Pred] [Assertion EPred] : Type (max (max (max (u + 1) v) w) w')
Std.WP.WPMonad.{u, v, w, w'} (m : Type u → Type v) (Pred : outParam (Type w)) (EPred : outParam (Type w')) [Monad m] [Assertion Pred] [Assertion EPred] : Type (max (max (max (u + 1) v) w) w')

Weakest precondition monad: a monad m whose weakest precondition interpretation is sound for pure and bind. The interpretation for every result type is carried as the toWP field; an instance exposes it as a WP (m α) … interpretation.

Std.WP.WPMonad.mk.{u, v, w, w'}
map_const : ∀ {α β : Type u}, Functor.mapConst = Functor.map ∘ Function.const β
Inherited from
  1. LawfulMonad m
id_map : ∀ {α : Type u} (x : m α), id <$> x = x
Inherited from
  1. LawfulMonad m
comp_map : ∀ {α β γ : Type u} (g : α → β) (h : β → γ) (x : m α), (h ∘ g) <$> x = h <$> g <$> x
Inherited from
  1. LawfulMonad m
seqLeft_eq : ∀ {α β : Type u} (x : m α) (y : m β), x <* y = Function.const β <$> x <*> y
Inherited from
  1. LawfulMonad m
seqRight_eq : ∀ {α β : Type u} (x : m α) (y : m β), x *> y = Function.const α id <$> x <*> y
Inherited from
  1. LawfulMonad m
pure_seq : ∀ {α β : Type u} (g : α → β) (x : m α), pure g <*> x = g <$> x
Inherited from
  1. LawfulMonad m
map_pure : ∀ {α β : Type u} (g : α → β) (x : α), g <$> pure x = pure (g x)
Inherited from
  1. LawfulMonad m
seq_pure : ∀ {α β : Type u} (g : m (α → β)) (x : α), g <*> pure x = (fun h => h x) <$> g
Inherited from
  1. LawfulMonad m
seq_assoc : ∀ {α β γ : Type u} (x : m α) (g : m (α → β)) (h : m (β → γ)), h <*> (g <*> x) = Function.comp <$> h <*> g <*> x
Inherited from
  1. LawfulMonad m
bind_pure_comp : ∀ {α β : Type u} (f : α → β) (x : m α),
  (do
      let a ← x
      pure (f a)) =
    f <$> x
Inherited from
  1. LawfulMonad m
bind_map : ∀ {α β : Type u} (f : m (α → β)) (x : m α),
  (do
      let x_1 ← f
      x_1 <$> x) =
    f <*> x
Inherited from
  1. LawfulMonad m
pure_bind : ∀ {α β : Type u} (x : α) (f : α → m β), pure x >>= f = f x
Inherited from
  1. LawfulMonad m
bind_assoc : ∀ {α β γ : Type u} (x : m α) (f : α → m β) (g : β → m γ), x >>= f >>= g = x >>= fun x => f x >>= g
Inherited from
  1. LawfulMonad m
toWP : (α : Type u) → WP (m α) α Pred EPred

The weakest precondition interpretation of m at every result type.

pure_le_wp_pure : ∀ {α : Type u} (x : α) (post : α → Pred) (epost : EPred), post x ⊑ wp (pure x) post epost

Soundness of pure: the postcondition applied to x implies the weakest precondition of pure x.

bind_le_wp_bind : ∀ {α β : Type u} (x : m α) (f : α → m β) (post : β → Pred) (epost : EPred),
  wp x (fun a => wp (f a) post epost) epost ⊑ wp (x >>= f) post epost

Soundness of bind: composing weakest preconditions is at least as strong as the weakest precondition of >>=.

Missing WPMonad Instance

The single-field structure Identity acts like the identity monad Id. It has a WP instance, but no WPMonad instance:

structure Identity (α : Type u) where run : α variable {α : Type u} instance : Monad Identity where pure x := ⟨x⟩ bind x f := f x.run instance : WP (Identity α) α Prop EStack⟨⟩ where wpTrans x := ⟨fun post _ => post x.run⟩ wp_trans_monotone x := fun _ _ _ _ _ hpost => hpost x.run theorem Identity.of_run_eq_wp {x : α} {prog : Identity α} (h : Identity.run prog = x) (P : α → Prop) (hwp : wp prog P ()) : P x := α:Type ux:αprog:Identity αh:prog.run = xP:α → Prophwp:wp prog P ()⊢ P x All goals completed! 🐙

The missing instance prevents vcgen from using its specifications for pure and bind. For example, the following function reverses a list with a Lean.Parser.Term.doFor : doElem`for x in e do s` iterates over `e` assuming `e`'s type has an instance of the `ForIn` typeclass. `break` and `continue` are supported inside `for` loops. `for x in e, x2 in e2, ... do s` iterates over the given collections in parallel, until at least one of them is exhausted. The types of `e2` etc. must implement the `Std.ToStream` typeclass. for loop in Identity:

def rev (xs : List α) : Identity (List α) := do let mut out := [] for x in xs do out := x :: out return out

The theorem rev_correct states that rev agrees with List.reverse. Without a WPMonad instance, vcgen cannot decompose rev into a sequence of steps in Id and reports that no specification applies to the program:

theorem rev_correct {xs : List α} : (rev xs).run = xs.reverse := α:Type uxs:List α⊢ (rev xs).run = xs.reverse α:Type uxs:List αx:List αh:(rev xs).run = x⊢ x = xs.reverse α:Type uxs:List αx:List αh:(rev xs).run = x⊢ wp (rev xs) (fun x => x = xs.reverse) () No spec applicable to program (forIn xs [] fun x __s => pure (ForInStep.yield (x :: __s))) >>= pure in monad Identity. Candidates were [SpecProof.global Std.WP.Spec.bind].All goals completed! 🐙
No spec applicable to program (forIn xs [] fun x __s => pure (ForInStep.yield (x :: __s))) >>=
  pure in monad Identity. Candidates were [SpecProof.global Std.WP.Spec.bind].

When no specification is applicable to bind, the problem is usually a missing WPMonad instance. The issue can be resolved by adding a suitable instance:

instance : WPMonad Identity Prop EStack⟨⟩ where toWP _ := inferInstance pure_le_wp_pure _ _ _ := PartialOrder.rel_refl bind_le_wp_bind _ _ _ _ := PartialOrder.rel_refl

With this instance, and a suitable invariant, vcgen and the grind-mode tactic finish can prove the theorem.

theorem rev_correct {xs : List α} : (rev xs).run = xs.reverse := α:Type uxs:List α⊢ (rev xs).run = xs.reverse α:Type uxs:List αx:List αh:(rev xs).run = x⊢ x = xs.reverse α:Type uxs:List αx:List αh:(rev xs).run = x⊢ wp (rev xs) (fun x => x = xs.reverse) () α:Type uxs:List αx:List αh:(rev xs).run = x⊢ wp (do let __s ← forIn xs [] fun x __s => pure (ForInStep.yield (x :: __s)) pure __s) (fun x => x = xs.reverse) () vcgen invariants · fun pref suff out => out = pref.reverse with All goals completed! 🐙

17.2.3.3. Soundness Lemmas🔗

Monads that can be invoked from pure code typically provide an invocation operator that takes any required input state as a parameter and returns either a value paired with an output state or some kind of exceptional value. Examples include StateT.run, ExceptT.run, and Id.run. Soundness lemmas provide a bridge between statements about invocations of monadic programs and those programs' weakest precondition semantics as given by their WP instances. They show that a property about the invocation is true if its weakest precondition is true.

🔗theorem
Std.WP.Id.of_run_eq_wp.{u} {α : Type u} {x : α} {prog : Id α} (h : prog.run = x) (P : α → Prop) (hwp : wp prog P ()) : P x
Std.WP.Id.of_run_eq_wp.{u} {α : Type u} {x : α} {prog : Id α} (h : prog.run = x) (P : α → Prop) (hwp : wp prog P ()) : P x

Soundness for Id: if wp prog P holds, then P holds of Id.run prog.

🔗theorem
Std.WP.StateM.of_run_eq_wp.{u_1} {α σ : Type u_1} {x : α × σ} {prog : StateM σ α} {s : σ} (h : StateT.run prog s = x) (P : α × σ → Prop) (hwp : wp prog (fun a s' => P (a, s')) () s) : P x
Std.WP.StateM.of_run_eq_wp.{u_1} {α σ : Type u_1} {x : α × σ} {prog : StateM σ α} {s : σ} (h : StateT.run prog s = x) (P : α × σ → Prop) (hwp : wp prog (fun a s' => P (a, s')) () s) : P x

Soundness for StateM: if wp prog P s holds, then P holds of the value and final state of StateT.run prog s.

🔗theorem
Std.WP.StateM.of_run'_eq_wp {α σ : Type} {x : α} {prog : StateM σ α} {s : σ} (h : StateT.run' prog s = x) (P : α → Prop) (hwp : wp prog (fun a x => P a) () s) : P x
Std.WP.StateM.of_run'_eq_wp {α σ : Type} {x : α} {prog : StateM σ α} {s : σ} (h : StateT.run' prog s = x) (P : α → Prop) (hwp : wp prog (fun a x => P a) () s) : P x

Soundness for StateM, at the value alone: if wp prog P s holds, then P holds of StateT.run' prog s.

🔗theorem
Std.WP.ReaderM.of_run_eq_wp {α ρ : Type} {x : α} {prog : ReaderM ρ α} {r : ρ} (h : ReaderT.run prog r = x) (P : α → Prop) (hwp : wp prog (fun a x => P a) () r) : P x
Std.WP.ReaderM.of_run_eq_wp {α ρ : Type} {x : α} {prog : ReaderM ρ α} {r : ρ} (h : ReaderT.run prog r = x) (P : α → Prop) (hwp : wp prog (fun a x => P a) () r) : P x

Soundness for ReaderM: if wp prog P r holds, then P holds of ReaderT.run prog r.

🔗theorem
Std.WP.Except.of_eq_wp {ε α : Type} {x prog : Except ε α} (h : prog = x) (P : Except ε α → Prop) (hwp : wp prog (fun a => P (Except.ok a)) fun e => P (Except.error e)) : P x
Std.WP.Except.of_eq_wp {ε α : Type} {x prog : Except ε α} (h : prog = x) (P : Except ε α → Prop) (hwp : wp prog (fun a => P (Except.ok a)) fun e => P (Except.error e)) : P x

Soundness for Except: the postcondition takes an ok case and an error case, and wp prog holds of prog itself.

🔗theorem
Std.WP.Option.of_eq_wp.{u} {α : Type u} {x prog : Option α} (h : prog = x) (P : Option α → Prop) (hwp : wp prog (fun a => P (some a)) fun x => P none) : P x
Std.WP.Option.of_eq_wp.{u} {α : Type u} {x prog : Option α} (h : prog = x) (P : Option α → Prop) (hwp : wp prog (fun a => P (some a)) fun x => P none) : P x

Soundness for Option: the postcondition takes a some case and a none case, and wp prog holds of prog itself.

🔗theorem
Std.WP.EStateM.of_run_eq_wp {ε σ α : Type} {x : EStateM.Result ε σ α} {prog : EStateM ε σ α} {s : σ} (h : prog.run s = x) (P : EStateM.Result ε σ α → Prop) (hwp : wp prog (fun a s' => P (EStateM.Result.ok a s')) (fun e s' => P (EStateM.Result.error e s')) s) : P x
Std.WP.EStateM.of_run_eq_wp {ε σ α : Type} {x : EStateM.Result ε σ α} {prog : EStateM ε σ α} {s : σ} (h : prog.run s = x) (P : EStateM.Result ε σ α → Prop) (hwp : wp prog (fun a s' => P (EStateM.Result.ok a s')) (fun e s' => P (EStateM.Result.error e s')) s) : P x

Soundness for EStateM: if wp prog P s holds, then P holds of (prog.run s).

17.2.4. Hoare Triples🔗

A Hoare triple (Hoare, 1969)Marginal noteC. A. R. Hoare (1969). “An Axiomatic Basis for Computer Programming”. Communications of the ACM. 12(10), pp. 576–583. consists of a precondition, a program, and a postcondition. Running the program in a state for which the precondition is true results in a state where the postcondition is true.

🔗structure
Std.WP.Triple.{u, v, w, w'} {Pred : Type w} {EPred : Type w'} {Prog : Type u} {Value : Type v} [Assertion Pred] [Assertion EPred] (x : Prog) [WP Prog Value Pred EPred] (pre : Pred) (post : Value → Pred) (epost : EPred) : Prop
Std.WP.Triple.{u, v, w, w'} {Pred : Type w} {EPred : Type w'} {Prog : Type u} {Value : Type v} [Assertion Pred] [Assertion EPred] (x : Prog) [WP Prog Value Pred EPred] (pre : Pred) (post : Value → Pred) (epost : EPred) : Prop

A Hoare triple for reasoning about programs. A Hoare triple Triple x pre post epost is a specification for x: if assertion pre holds before x, then postcondition post holds after running x (and epost handles any errors).

Std.WP.Triple.intro.{u, v, w, w'}

Construct a triple from a weakest precondition entailment.

le_wp : pre ⊑ wp x post epost

The weakest precondition entailment witnessing the triple.

syntaxHoare Triples
term ::= ...
    | Hoare triple notation without exception postcondition (defaults to `⊥`). An optional `(m := …)`
after the precondition ascribes the program to monad `…`. ⦃ term ⦄ term ⦃ term ⦄
term ::= ...
    | Hoare triple notation with an exception postcondition:
`⦃ P ⦄ x ⦃ Q; E ⦄ := Triple x P Q E`. ⦃ term ⦄ term ⦃ term; term ⦄

⦃ P ⦄ x ⦃ Q; E ⦄ is syntactic sugar for Triple x P Q E. When the exception postcondition is omitted, as in ⦃ P ⦄ x ⦃ Q ⦄, it defaults to the bottom assertion ⊥, asserting that the program does not throw an exception.

🔗theorem
Std.WP.Triple.and.{u, v, w, w'} {Prog : Type u} {Value : Type v} {Pred : Type w} {EPred : Type w'} [Assertion Pred] [Assertion EPred] [WP Prog Value Pred EPred] {x : Prog} [WPConjunctive x] {pre₁ pre₂ : Pred} {post₁ post₂ : Value → Pred} {epost₁ epost₂ : EPred} (h₁ : ⦃ pre₁ ⦄ x ⦃ post₁; epost₁ ⦄) (h₂ : ⦃ pre₂ ⦄ x ⦃ post₂; epost₂ ⦄) : ⦃ pre₁ ⊓ pre₂ ⦄ x ⦃ post₁ ⊓ post₂; epost₁ ⊓ epost₂ ⦄
Std.WP.Triple.and.{u, v, w, w'} {Prog : Type u} {Value : Type v} {Pred : Type w} {EPred : Type w'} [Assertion Pred] [Assertion EPred] [WP Prog Value Pred EPred] {x : Prog} [WPConjunctive x] {pre₁ pre₂ : Pred} {post₁ post₂ : Value → Pred} {epost₁ epost₂ : EPred} (h₁ : ⦃ pre₁ ⦄ x ⦃ post₁; epost₁ ⦄) (h₂ : ⦃ pre₂ ⦄ x ⦃ post₂; epost₂ ⦄) : ⦃ pre₁ ⊓ pre₂ ⦄ x ⦃ post₁ ⊓ post₂; epost₁ ⊓ epost₂ ⦄

Conjunction of two Hoare triple specifications for a program x. This theorem decomposes proofs: prove unrelated facts about x separately, then combine them here.

🔗theorem
Std.WP.Triple.mp.{u, v, w, w'} {Prog : Type u} {Value : Type v} {Pred : Type w} {EPred : Type w'} [Assertion Pred] [Assertion EPred] [WP Prog Value Pred EPred] {x : Prog} [WPConjunctive x] [Heyting Pred] [Heyting EPred] {pre₁ pre₂ : Pred} {post₁ post₂ : Value → Pred} {epost₁ epost₂ : EPred} (h₁ : ⦃ pre₁ ⦄ x ⦃ post₁; epost₁ ⦄) (h₂ : ⦃ pre₂ ⦄ x ⦃ post₁ ⇨ post₂; epost₁ ⇨ epost₂ ⦄) : ⦃ pre₁ ⊓ pre₂ ⦄ x ⦃ post₁ ⊓ post₂; epost₁ ⊓ epost₂ ⦄
Std.WP.Triple.mp.{u, v, w, w'} {Prog : Type u} {Value : Type v} {Pred : Type w} {EPred : Type w'} [Assertion Pred] [Assertion EPred] [WP Prog Value Pred EPred] {x : Prog} [WPConjunctive x] [Heyting Pred] [Heyting EPred] {pre₁ pre₂ : Pred} {post₁ post₂ : Value → Pred} {epost₁ epost₂ : EPred} (h₁ : ⦃ pre₁ ⦄ x ⦃ post₁; epost₁ ⦄) (h₂ : ⦃ pre₂ ⦄ x ⦃ post₁ ⇨ post₂; epost₁ ⇨ epost₂ ⦄) : ⦃ pre₁ ⊓ pre₂ ⦄ x ⦃ post₁ ⊓ post₂; epost₁ ⊓ epost₂ ⦄

Modus ponens for two Hoare triple specifications of a program x. This theorem separates proofs. Let h₁ establish a basic postcondition post₁ for x, and let h₂ establish the advanced postcondition post₂ under the assumption post₁. Then mp h₁ h₂ establishes post₂ for x.

17.2.5. Specification Lemmas🔗

Specification lemmas are designated theorems that associate a Hoare triple with a program construct, such as bind, pure, a loop, or an application of a library function. A given program construct may have multiple specification lemmas that are applicable in different situations; they are tried in priority order until one can be instantiated.

The vcgen tactic decomposes a goal P ⊑ wp prog Q E by applying a specification lemma whose program matches prog, as the section on verification conditions describes. If no specification lemma applies to prog, then vcgen reports an error that names prog and the candidate lemmas. Specification lemmas make reasoning about programs modular: the specification lemma of a function f is proved once, and vcgen applies it at every call of f without unfolding the definition of f. In this respect, pure and bind are ordinary functions: the specification lemmas Spec.pure and Spec.bind hold in every monad with a WPMonad instance.

When applied to a theorem whose statement is a Hoare triple, the spec attribute registers the theorem as a specification lemma. These lemmas are used in order of priority.

The spec attribute may also be applied to definitions. On definitions, it indicates that the definition should be unfolded during verification condition generation.

attributeSpecification Lemmas
attr ::= ...
    | Theorems tagged with the `spec` attribute are used by the `mspec` and `mvcgen` tactics.

* When used on a theorem `foo_spec : Triple (foo a b c) P Q`, then `mspec` and `mvcgen` will use
  `foo_spec` as a specification for calls to `foo`.
* Otherwise, when used on a definition that `@[simp]` would work on, it is added to the internal
  simp set of `mvcgen` that is used within `wp⟦·⟧` contexts to simplify match discriminants and
  applications of constants.
spec prio?

Theorems tagged with the spec attribute are used by the mspec and mvcgen tactics.

  • When used on a theorem foo_spec : Triple (foo a b c) P Q, then mspec and mvcgen will use foo_spec as a specification for calls to foo.

  • Otherwise, when used on a definition that @[simp] would work on, it is added to the internal simp set of mvcgen that is used within wp⟦·⟧ contexts to simplify match discriminants and applications of constants.

A logical variable is a universally quantified variable of a specification lemma that does not occur in the program. Logical variables are used to relate the initial state to the final state and the return value.

An auto-framing specification is a specification lemma whose postcondition is a universally quantified variable, with a precondition that is stated in terms of that variable. When vcgen applies an auto-framing specification, unification instantiates the postcondition with the assertion that the rest of the program requires; this means that there's no need to prove that the postcondition entails the subsequent precondition. Every fact that the postcondition states about parts of the state that the program does not change is preserved without further proof. As an example, the specification lemma Spec.bind is auto-framing: its precondition is the weakest precondition of the first action with respect to the weakest precondition of the second.

Std.WP.Spec.bind.{u, v, w, w'} {m : Type u → Type v} [Monad m] {Pred : Type w} {EPred : Type w'} [Assertion Pred] [Assertion EPred] [WPMonad m Pred EPred] {α β : Type u} {post : β → Pred} {epost : EPred} (x : m α) (f : α → m β) : ⦃ wp x (fun a => binderNameHint a f (wp (f a) post epost)) epost ⦄ x >>= f ⦃ post; epost ⦄
Logical Variables

The function double doubles the value of a Nat state:

def double : StateM Nat Unit := do modify (2 * ·)

Its specification should relate the initial and final states, but it cannot know their precise values. The specification uses a logical variable to stand for the initial state:

theorem double_spec {n : Nat} : ⦃ fun s => s = n ⦄ double ⦃ fun _ s => s = 2 * n ⦄ := n:Nat⊢ ⦃ fun s => s = n ⦄ double ⦃ fun x s => s = 2 * n ⦄ n:Nat⊢ ⦃ fun s => s = n ⦄ modify fun x => 2 * x ⦃ fun x s => s = 2 * n ⦄ vcgen with All goals completed! 🐙

The assertion in the precondition is a function because the assertion type of StateM Nat is Nat → Prop.

17.2.6. Invariant Specifications🔗

The specification lemmas for ForIn.forIn and ForIn'.forIn' take parameters of type Invariant, and vcgen ensures that invariants are not accidentally generated by other automation.

🔗def
Std.WP.Invariant.{u₁, u₂, uₚ} (α : Type u₁) (β : Type u₂) (Pred : Type uₚ) : Type (max (max u₂ uₚ) u₁)
Std.WP.Invariant.{u₁, u₂, uₚ} (α : Type u₁) (β : Type u₂) (Pred : Type uₚ) : Type (max (max u₂ uₚ) u₁)

The type of loop invariants used by the specifications of for ... in ... loops. A loop invariant maps the elements consumed so far, the elements remaining, and the accumulator state to an assertion.

Invariants use lists to model the sequence of values in a Lean.Parser.Term.doFor : doElem`for x in e do s` iterates over `e` assuming `e`'s type has an instance of the `ForIn` typeclass. `break` and `continue` are supported inside `for` loops. `for x in e, x2 in e2, ... do s` iterates over the given collections in parallel, until at least one of them is exhausted. The types of `e2` etc. must implement the `Std.ToStream` typeclass. for loop. An invariant is a function of the prefix of elements that the loop has already consumed, the suffix of elements that remain, the current accumulator state, and any further state arguments of the monad's assertion language.

🔗def
Std.WP.Invariant.withEarlyReturnNewDo.{u₁, u₂, u_1} {α : Type u₁} {β γ : Type u₂} (Pred : Type u_1) [Assertion Pred] (onContinue : List α → List α → β → Pred) (onReturn : γ → β → Pred) : Invariant α (Option γ × β) Pred
Std.WP.Invariant.withEarlyReturnNewDo.{u₁, u₂, u_1} {α : Type u₁} {β γ : Type u₂} (Pred : Type u_1) [Assertion Pred] (onContinue : List α → List α → β → Pred) (onReturn : γ → β → Pred) : Invariant α (Option γ × β) Pred

An invariant combinator for loops with early return, for the new do elaborator which uses Prod for the state tuple: onContinue is the invariant while iterating, onReturn holds once the loop returned early with a value.