The Lean Language Reference

18.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 mvcgen 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 are 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. SPred is a type of predicates that is parameterized over a monadic state, expressed as a list of the types of the fields that make up the state. The usual logical connectives and quantifiers are defined for SPred. Each monad that can be used with mvcgen is assigned a state type by an instance of WP, and Assertion is the corresponding type of assertions for that monad, which is used for preconditions. Assertion is a wrapper around SPred: while SPred is parameterized by a list of states types, Assertion is parameterized by a more informative type that it translates to a list of state types for SPred. A PostCond pairs an Assertion about a return value with assertions about potential exceptions; the available exceptions are also specified by the monad's WP instance.

18.2.1. Stateful Predicates

The predicate transformer semantics of monadic 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 monads have different state types available, but each individual state always has a type. Given a list of state types, SPred is a type of predicates over these states.

SPred is not inherently tied to the monadic verification framework. The related Assertion computes a suitable SPred for a monad's state as expressed via its WP instance's PostShape output parameter.

🔗def
Std.Do.SPred.{u} (σs : List (Type u)) : Type u
Std.Do.SPred.{u} (σs : List (Type u)) : Type u

A predicate indexed by a list of states.

example : SPred [Nat, Bool] = (Nat → Bool → ULift Prop) := rfl

Ordinary propositions that do not mention the state can be used as stateful predicates by adding a trivial universal quantification. This is written with the syntax P, which is syntactic sugar for SPred.pure.

syntaxNotation for SPred
term ::= ...
    | Embedding of pure Lean values into `SVal`. An alias for `SPred.pure`. term

Embedding of pure Lean values into SVal. An alias for SPred.pure.

🔗def
Std.Do.SPred.pure.{u} {σs : List (Type u)} (P : Prop) : SPred σs
Std.Do.SPred.pure.{u} {σs : List (Type u)} (P : Prop) : SPred σs

A pure proposition P : Prop embedded into SPred. Prefer to use idiom bracket notation `⌜P⌝.

Stateful Predicates

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

def ItIsSecret : SPred [String] := fun s => s = "secret"

18.2.1.1. Entailment

Stateful predicates are related by entailment. Entailment of stateful predicates is defined as universally-quantified implication: if P and Q are predicates over a state \sigma, then P entails Q (written P \vdash_s Q) when ∀ s : \sigma, P(s) → Q(s).

🔗def
Std.Do.SPred.entails.{u} {σs : List (Type u)} (P Q : SPred σs) : Prop
Std.Do.SPred.entails.{u} {σs : List (Type u)} (P Q : SPred σs) : Prop

Entailment in SPred.

🔗def
Std.Do.SPred.bientails.{u} {σs : List (Type u)} (P Q : SPred σs) : Prop
Std.Do.SPred.bientails.{u} {σs : List (Type u)} (P Q : SPred σs) : Prop

Equivalence relation on SPred. Convert to Eq via bientails.to_eq.

syntaxNotation for SPred
term ::= ...
    | Entailment in `SPred`; sugar for `SPred.entails`. term ⊢ₛ term

Entailment in SPred; sugar for SPred.entails.

term ::= ...
    | Tautology in `SPred`; sugar for `SPred.entails ⌜True⌝`. ⊢ₛ term

Tautology in SPred; sugar for SPred.entails True.

term ::= ...
    | Bi-entailment in `SPred`; sugar for `SPred.bientails`. term ⊣⊢ₛ term

Bi-entailment in SPred; sugar for SPred.bientails.

The logic of stateful predicates includes an implication connective. The difference between entailment and implication is that entailment is a statement in Lean's logic, while implication is internal to the stateful logic. Given stateful predicates P and Q for state σ, P ⊢ₛ Q is a Prop while spred(P Q) is an SPred σ.

18.2.1.2. Notation

The syntax of stateful predicates overlaps with that of ordinary Lean terms. In particular, stateful predicates use the usual syntax for logical connectives and quantifiers. The syntax associated with stateful predicates is automatically enabled in contexts such as pre- and postconditions where they are clearly intended; other contexts must explicitly opt in to the syntax using Std.Do.«termSpred(_)» : termspred. The usual meanings of these operators can be recovered by using the Std.Do.«termTerm(_)» : termterm operator.

syntaxPredicate Terms

Std.Do.«termSpred(_)» : termspred indicates that logical connectives and quantifiers should be understood as those pertaining to stateful predicates, while Std.Do.«termTerm(_)» : termterm indicates that they should have the usual meaning.

term ::= ...
    | spred(term)
term ::= ...
    | term(term)

18.2.1.3. Connectives and Quantifiers

syntaxPredicate Connectives
term ::= ...
    | spred(`And a b`, or `a ∧ b`, is the conjunction of propositions. It can be
constructed and destructed like a pair: if `ha : a` and `hb : b` then
`⟨ha, hb⟩ : a ∧ b`, and if `h : a ∧ b` then `h.left : a` and `h.right : b`.


Conventions for notations in identifiers:

 * The recommended spelling of `∧` in identifiers is `and`.term  term)

Syntactic sugar for SPred.and.

term ::= ...
    | spred(`Or a b`, or `a ∨ b`, is the disjunction of propositions. There are two
constructors for `Or`, called `Or.inl : a → a ∨ b` and `Or.inr : b → a ∨ b`,
and you can use `match` or `cases` to destruct an `Or` assumption into the
two cases.


Conventions for notations in identifiers:

 * The recommended spelling of `∨` in identifiers is `or`.term  term)

Syntactic sugar for SPred.or.

term ::= ...
    | spred(`Not p`, or `¬p`, is the negation of `p`. It is defined to be `p → False`,
so if your goal is `¬p` you can use `intro h` to turn the goal into
`h : p ⊢ False`, and if you have `hn : ¬p` and `h : p` then `hn h : False`
and `(hn h).elim` will prove anything.
For more information: [Propositional Logic](https://lean-lang.org/theorem_proving_in_lean4/propositions_and_proofs.html#propositional-logic)


Conventions for notations in identifiers:

 * The recommended spelling of `¬` in identifiers is `not`.¬ term)

Syntactic sugar for SPred.not.

term ::= ...
    | spred(term  term)

Syntactic sugar for SPred.imp.

term ::= ...
    | spred(If and only if, or logical bi-implication. `a ↔ b` means that `a` implies `b` and vice versa.
By `propext`, this implies that `a` and `b` are equal and hence any expression involving `a`
is equivalent to the corresponding expression with `b` instead.


Conventions for notations in identifiers:

 * The recommended spelling of `↔` in identifiers is `iff`.term  term)

Syntactic sugar for SPred.iff.

🔗def
Std.Do.SPred.and.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs
Std.Do.SPred.and.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs

Conjunction in SPred.

🔗def
Std.Do.SPred.conjunction.{u} {σs : List (Type u)} (env : List (SPred σs)) : SPred σs
Std.Do.SPred.conjunction.{u} {σs : List (Type u)} (env : List (SPred σs)) : SPred σs

Conjunction of a list of SPred.

🔗def
Std.Do.SPred.or.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs
Std.Do.SPred.or.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs

Disjunction in SPred.

🔗def
Std.Do.SPred.not.{u} {σs : List (Type u)} (P : SPred σs) : SPred σs
Std.Do.SPred.not.{u} {σs : List (Type u)} (P : SPred σs) : SPred σs

Negation in SPred.

🔗def
Std.Do.SPred.imp.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs
Std.Do.SPred.imp.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs

Implication in SPred.

🔗def
Std.Do.SPred.iff.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs
Std.Do.SPred.iff.{u} {σs : List (Type u)} (P Q : SPred σs) : SPred σs

Biconditional in SPred.

syntaxPredicate Quantifiers
term ::= ...
    | spred( ident, term)
term ::= ...
    | spred( ident : term,  term)
term ::= ...
    | spred( Explicit binder, like `(x y : A)` or `(x y)`.
Default values can be specified using `(x : A := v)` syntax, and tactics using `(x : A := by tac)`.
(ident (ident | A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
hole)* : term),  term)
term ::= ...
    | spred( A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
_, term)
term ::= ...
    | spred( A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
_ : term,  term)
term ::= ...
    | spred( Explicit binder, like `(x y : A)` or `(x y)`.
Default values can be specified using `(x : A := v)` syntax, and tactics using `(x : A := by tac)`.
(A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
_ (ident | A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
hole)* : term),  term)

Each form of universal quantification is syntactic sugar for an invocation of SPred.forall on a function that takes the quantified variable as a parameter.

term ::= ...
    | spred( `binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
ident, term)
term ::= ...
    | spred( `binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
ident : term,  term)
term ::= ...
    | spred( (`binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
ident `binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
binderIdent* : term),  term)
term ::= ...
    | spred( `binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
_, term)
term ::= ...
    | spred( `binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
_ : term,  term)
term ::= ...
    | spred( (`binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.

The way this works is that holes create fresh metavariables.
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
This is often known as *unification*.

Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
* In `match` patterns, holes are catch-all patterns.
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.

Related concept: implicit parameters are automatically filled in with holes during the elaboration process.

See also `?m` syntax (synthetic holes).
_ `binderIdent` matches an `ident` or a `_`. It is used for identifiers in binding
position, where `_` means that the value should be left unnamed and inaccessible.
binderIdent* : term),  term)

Each form of existential quantification is syntactic sugar for an invocation of SPred.exists on a function that takes the quantified variable as a parameter.

🔗def
Std.Do.SPred.forall.{u, v} {α : Sort u} {σs : List (Type v)} (P : α SPred σs) : SPred σs
Std.Do.SPred.forall.{u, v} {α : Sort u} {σs : List (Type v)} (P : α SPred σs) : SPred σs

Universal quantifier in SPred.

🔗def
Std.Do.SPred.exists.{u, v} {α : Sort u} {σs : List (Type v)} (P : α SPred σs) : SPred σs
Std.Do.SPred.exists.{u, v} {α : Sort u} {σs : List (Type v)} (P : α SPred σs) : SPred σs

Existential quantifier in SPred.

18.2.1.4. Stateful Values

Just as SPred represents predicate over states, SVal represents a value that is derived from a state.

🔗def
Std.Do.SVal.{u} (σs : List (Type u)) (α : Type u) : Type u
Std.Do.SVal.{u} (σs : List (Type u)) (α : Type u) : Type u

A value indexed by a curried tuple of states.

example : SVal [Nat, Bool] String = (Nat → Bool → String) := rfl
🔗def
Std.Do.SVal.getThe.{u} {σs : List (Type u)} (σ : Type u) [SVal.GetTy σ σs] : SVal σs σ
Std.Do.SVal.getThe.{u} {σs : List (Type u)} (σ : Type u) [SVal.GetTy σ σs] : SVal σs σ

Get the top-most state of type σ from an SVal.

🔗def
Std.Do.SVal.StateTuple.{u} (σs : List (Type u)) : Type u
Std.Do.SVal.StateTuple.{u} (σs : List (Type u)) : Type u

A tuple capturing the whole state of an SVal.

🔗def
Std.Do.SVal.curry.{u} {α : Type u} {σs : List (Type u)} (f : SVal.StateTuple σs α) : SVal σs α
Std.Do.SVal.curry.{u} {α : Type u} {σs : List (Type u)} (f : SVal.StateTuple σs α) : SVal σs α

Curry a function taking a StateTuple into an SVal.

🔗def
Std.Do.SVal.uncurry.{u} {α : Type u} {σs : List (Type u)} (f : SVal σs α) : SVal.StateTuple σs α
Std.Do.SVal.uncurry.{u} {α : Type u} {σs : List (Type u)} (f : SVal σs α) : SVal.StateTuple σs α

Uncurry an SVal into a function taking a StateTuple.

18.2.2. Assertions

The language of assertions about monadic programs is parameterized by a postcondition shape, which describes the inputs to and outputs from a computation in a given monad. Preconditions may mention the initial values of the monad's state, while postconditions may mention the returned value, the final values of the monad's state, and must furthermore account for any exceptions that could have been thrown. The postcondition shape of a given monad determines the states and exceptions in the monad. PostShape.pure describes a monad in which assertions may not mention any states, PostShape.arg describes a state value, and PostShape.except describes a possible exception. Because these constructors can be continually added, the postcondition shape of a monad transformer can be defined in terms of the postcondition shape of the underlying transformed monad. Behind the scenes, an Assertion is translated into an appropriate SPred by translating the postcondition shape into a list of state types, discarding exceptions.

🔗inductive type
Std.Do.PostShape.{u} : Type (u + 1)
Std.Do.PostShape.{u} : Type (u + 1)

Constructors

pure.{u} : PostShape
arg.{u} (σ : Type u) : PostShape  PostShape
except.{u} (ε : Type u) : PostShape  PostShape
🔗def
Std.Do.PostShape.args.{u} : PostShape List (Type u)
Std.Do.PostShape.args.{u} : PostShape List (Type u)
🔗def
Std.Do.Assertion.{u} (ps : PostShape) : Type u
Std.Do.Assertion.{u} (ps : PostShape) : Type u

An assertion on the .args in the given predicate shape.

example : Assertion (.arg ρ .pure) = (ρ → ULift Prop) := rfl
example : Assertion (.except ε .pure) = ULift Prop := rfl
example : Assertion (.arg σ (.except ε .pure)) = (σ → ULift Prop) := rfl
example : Assertion (.except ε (.arg σ .pure)) = (σ → ULift Prop) := rfl

This is an abbreviation for SPred under the hood, so all theorems about SPred apply.

🔗def
Std.Do.PostCond.{u} (α : Type u) (ps : PostShape) : Type u
Std.Do.PostCond.{u} (α : Type u) (ps : PostShape) : Type u

A postcondition for the given predicate shape, with one Assertion for the terminating case and one Assertion for each .except layer in the predicate shape.

variable (α σ ε : Type)
example : PostCond α (.arg σ .pure) = ((α → σ → ULift Prop) × PUnit) := rfl
example : PostCond α (.except ε .pure) = ((α → ULift Prop) × (ε → ULift Prop) × PUnit) := rfl
example : PostCond α (.arg σ (.except ε .pure)) = ((α → σ → ULift Prop) × (ε → ULift Prop) × PUnit) := rfl
example : PostCond α (.except ε (.arg σ .pure)) = ((α → σ → ULift Prop) × (ε → σ → ULift Prop) × PUnit) := rfl
syntaxPostconditions
term ::= ...
    | A postcondition expressing total correctness.
That is, it expresses that the asserted computation finishes without throwing an exception
*and* the result satisfies the given predicate `p`.
 term* => term

Syntactic sugar for a nested sequence of product constructors, terminating in (), in which the first element is an assertion about non-exceptional return values and the remaining elements are assertions about the exceptional cases for a postcondition.

🔗def
Std.Do.ExceptConds.{u} : PostShape Type u
Std.Do.ExceptConds.{u} : PostShape Type u

Encodes one continuation barrel for each PostShape.except in the given predicate shape.

example : ExceptConds (.pure) = Unit := rfl
example : ExceptConds (.except ε .pure) = ((ε → ULift Prop) × Unit) := rfl
example : ExceptConds (.arg σ (.except ε .pure)) = ((ε → ULift Prop) × Unit) := rfl
example : ExceptConds (.except ε (.arg σ .pure)) = ((ε → σ → ULift Prop) × Unit) := rfl

Postconditions for programs that might throw exceptions come in two varieties. The total correctness interpretation P prog r => Q' r asserts that, given P holds, then prog terminates and Q' holds for the result. The partial correctness interpretation P prog ⇓? r => Q' r asserts that, given P holds, and if prog terminates then Q' holds for the result.

syntaxException-Free Postconditions
term ::= ...
    | A postcondition expressing total correctness.
That is, it expresses that the asserted computation finishes without throwing an exception
*and* the result satisfies the given predicate `p`.
 term* => term

A postcondition expressing total correctness. That is, it expresses that the asserted computation finishes without throwing an exception and the result satisfies the given predicate p.

🔗def
Std.Do.PostCond.noThrow.{u} {ps : PostShape} {α : Type u} (p : α Assertion ps) : PostCond α ps
Std.Do.PostCond.noThrow.{u} {ps : PostShape} {α : Type u} (p : α Assertion ps) : PostCond α ps

A postcondition expressing total correctness. That is, it expresses that the asserted computation finishes without throwing an exception and the result satisfies the given predicate p.

syntaxPartial Postconditions
term ::= ...
    | A postcondition expressing partial correctness.
That is, it expresses that *if* the asserted computation finishes without throwing an exception
*then* the result satisfies the given predicate `p`.
Nothing is asserted when the computation throws an exception.
⇓? term* => term

A postcondition expressing partial correctness. That is, it expresses that if the asserted computation finishes without throwing an exception then the result satisfies the given predicate p. Nothing is asserted when the computation throws an exception.

🔗def
Std.Do.PostCond.mayThrow.{u} {ps : PostShape} {α : Type u} (p : α Assertion ps) : PostCond α ps
Std.Do.PostCond.mayThrow.{u} {ps : PostShape} {α : Type u} (p : α Assertion ps) : PostCond α ps

A postcondition expressing partial correctness. That is, it expresses that if the asserted computation finishes without throwing an exception then the result satisfies the given predicate p. Nothing is asserted when the computation throws an exception.

syntaxPostcondition Entailment
term ::= ...
    | term ⊢ₚ term

Syntactic sugar for PostCond.entails

🔗def
Std.Do.PostCond.entails.{u} {ps : PostShape} {α : Type u} (p q : PostCond α ps) : Prop
Std.Do.PostCond.entails.{u} {ps : PostShape} {α : Type u} (p q : PostCond α ps) : Prop
syntaxPostcondition Conjunction
term ::= ...
    | term ∧ₚ term

Syntactic sugar for PostCond.and

🔗def
Std.Do.PostCond.and.{u} {ps : PostShape} {α : Type u} (p q : PostCond α ps) : PostCond α ps
Std.Do.PostCond.and.{u} {ps : PostShape} {α : Type u} (p q : PostCond α ps) : PostCond α ps
syntaxPostcondition Implication
term ::= ...
    | term →ₚ term

Syntactic sugar for PostCond.imp

🔗def
Std.Do.PostCond.imp.{u} {ps : PostShape} {α : Type u} (p q : PostCond α ps) : PostCond α ps
Std.Do.PostCond.imp.{u} {ps : PostShape} {α : Type u} (p q : PostCond α ps) : PostCond α ps

18.2.3. Predicate Transformers

A predicate transformer is a function from postconditions for some postcondition state into assertions for that state. The function must be conjunctive, which means it must distribute over PostCond.and.

🔗structure
Std.Do.PredTrans.{u} (ps : PostShape) (α : Type u) : Type u
Std.Do.PredTrans.{u} (ps : PostShape) (α : Type u) : Type u

The type of predicate transformers for a given ps : PostShape and return type α : Type. A predicate transformer x : PredTrans ps α is a function that takes a postcondition Q : PostCond α ps and returns a precondition x.apply Q : Assertion ps.

Constructor

Std.Do.PredTrans.mk.{u}

Fields

apply : PostCond α ps  Assertion ps

Apply the predicate transformer to a postcondition.

conjunctive : PredTrans.Conjunctive self.apply

The predicate transformer is conjunctive: t (Q₁ ∧ₚ Q₂) ⊣⊢ₛ t Q₁ t Q₂. So the stronger the postcondition, the stronger the resulting precondition.

🔗def
Std.Do.PredTrans.Conjunctive.{u} {ps : PostShape} {α : Type u} (t : PostCond α ps Assertion ps) : Prop
Std.Do.PredTrans.Conjunctive.{u} {ps : PostShape} {α : Type u} (t : PostCond α ps Assertion ps) : Prop

Transforming a conjunction of postconditions is the same as the conjunction of transformed postconditions.

🔗def
Std.Do.PredTrans.Monotonic.{u} {ps : PostShape} {α : Type u} (t : PostCond α ps Assertion ps) : Prop
Std.Do.PredTrans.Monotonic.{u} {ps : PostShape} {α : Type u} (t : PostCond α ps Assertion ps) : Prop

The stronger the postcondition, the stronger the transformed precondition.

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

🔗def
Std.Do.PredTrans.pure.{u} {ps : PostShape} {α : Type u} (a : α) : PredTrans ps α
Std.Do.PredTrans.pure.{u} {ps : PostShape} {α : Type u} (a : α) : PredTrans ps α
🔗def
Std.Do.PredTrans.bind.{u} {ps : PostShape} {α β : Type u} (x : PredTrans ps α) (f : α PredTrans ps β) : PredTrans ps β
Std.Do.PredTrans.bind.{u} {ps : PostShape} {α β : Type u} (x : PredTrans ps α) (f : α PredTrans ps β) : PredTrans ps β

The helper operators PredTrans.pushArg, PredTrans.pushExcept, and PredTrans.pushOption 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
Std.Do.PredTrans.pushArg.{u} {ps : PostShape} {α σ : Type u} (x : StateT σ (PredTrans ps) α) : PredTrans (PostShape.arg σ ps) α
Std.Do.PredTrans.pushArg.{u} {ps : PostShape} {α σ : Type u} (x : StateT σ (PredTrans ps) α) : PredTrans (PostShape.arg σ ps) α
🔗def
Std.Do.PredTrans.pushExcept.{u_1} {ps : PostShape} {α ε : Type u_1} (x : ExceptT ε (PredTrans ps) α) : PredTrans (PostShape.except ε ps) α
Std.Do.PredTrans.pushExcept.{u_1} {ps : PostShape} {α ε : Type u_1} (x : ExceptT ε (PredTrans ps) α) : PredTrans (PostShape.except ε ps) α
🔗def
Std.Do.PredTrans.pushOption.{u_1} {ps : PostShape} {α : Type u_1} (x : OptionT (PredTrans ps) α) : PredTrans (PostShape.except PUnit ps) α
Std.Do.PredTrans.pushOption.{u_1} {ps : PostShape} {α : Type u_1} (x : OptionT (PredTrans ps) α) : PredTrans (PostShape.except PUnit ps) α

18.2.3.1. Weakest Preconditions

The weakest precondition semantics of a monad are provided by the WP type class. Instances of WP determine the monad's postcondition shape and provide the logical rules for interpreting the monad's operations as a predicate transformer in its postcondition shape.

🔗type class
Std.Do.WP.{u, v} (m : Type u Type v) (ps : outParam PostShape) : Type (max (u + 1) v)
Std.Do.WP.{u, v} (m : Type u Type v) (ps : outParam PostShape) : Type (max (u + 1) v)

A weakest precondition interpretation of a monadic program x : m α in terms of a predicate transformer PredTrans ps α. The monad m determines ps : PostShape. See the module comment for more details.

Instance Constructor

Std.Do.WP.mk.{u, v}

Methods

wp : {α : Type u}  m α  PredTrans ps α

Interpret a monadic program x : m α in terms of a predicate transformer PredTrans ps α.

syntaxWeakest Preconditions
term ::= ...
    | `wp⟦x⟧ Q` is defined as `(WP.wp x).apply Q`. wp⟦term (: term)?

wp⟦x Q is defined as (WP.wp x).apply Q.

18.2.3.2. Weakest Precondition Monad Morphisms

Most of the built-in specification lemmas for mvcgen relies on the presence of a WPMonad instance, in addition to the WP instance. In addition to being lawful, weakest preconditions of the monad's implementations of pure and bind should correspond to the pure and bind operators for the predicate transformer monad. Without a WPMonad instance, mvcgen typically returns the original proof goal unchanged.

🔗type class
Std.Do.WPMonad.{u, v} (m : Type u Type v) (ps : outParam PostShape) [Monad m] : Type (max (u + 1) v)
Std.Do.WPMonad.{u, v} (m : Type u Type v) (ps : outParam PostShape) [Monad m] : Type (max (u + 1) v)

A WP that is also a monad morphism, preserving pure and bind. (They all are.)

Instance Constructor

Std.Do.WPMonad.mk.{u, v}

Extends

Methods

map_const :  {α β : Type u}, Functor.mapConst = Functor.map  Function.const β
Inherited from
  1. LawfulMonad m
  2. WP m ps
id_map :  {α : Type u} (x : m α), id <$> x = x
Inherited from
  1. LawfulMonad m
  2. WP m ps
comp_map :  {α β γ : Type u} (g : α  β) (h : β  γ) (x : m α), (h  g) <$> x = h <$> g <$> x
Inherited from
  1. LawfulMonad m
  2. WP m ps
seqLeft_eq :  {α β : Type u} (x : m α) (y : m β), x <* y = Function.const β <$> x <*> y
Inherited from
  1. LawfulMonad m
  2. WP m ps
seqRight_eq :  {α β : Type u} (x : m α) (y : m β), x *> y = Function.const α id <$> x <*> y
Inherited from
  1. LawfulMonad m
  2. WP m ps
pure_seq :  {α β : Type u} (g : α  β) (x : m α), pure g <*> x = g <$> x
Inherited from
  1. LawfulMonad m
  2. WP m ps
map_pure :  {α β : Type u} (g : α  β) (x : α), g <$> pure x = pure (g x)
Inherited from
  1. LawfulMonad m
  2. WP m ps
seq_pure :  {α β : Type u} (g : m (α  β)) (x : α), g <*> pure x = (fun h => h x) <$> g
Inherited from
  1. LawfulMonad m
  2. WP m ps
seq_assoc :  {α β γ : Type u} (x : m α) (g : m (α  β)) (h : m (β  γ)), h <*> (g <*> x) = Function.comp <$> h <*> g <*> x
Inherited from
  1. LawfulMonad m
  2. WP m ps
bind_pure_comp :  {α β : Type u} (f : α  β) (x : m α),
  (do
      let a  x
      pure (f a)) =
    f <$> x
Inherited from
  1. LawfulMonad m
  2. WP m ps
bind_map :  {α β : Type u} (f : m (α  β)) (x : m α),
  (do
      let x_1  f
      x_1 <$> x) =
    f <*> x
Inherited from
  1. LawfulMonad m
  2. WP m ps
pure_bind :  {α β : Type u} (x : α) (f : α  m β), pure x >>= f = f x
Inherited from
  1. LawfulMonad m
  2. WP m ps
bind_assoc :  {α β γ : Type u} (x : m α) (f : α  m β) (g : β  m γ), x >>= f >>= g = x >>= fun x => f x >>= g
Inherited from
  1. LawfulMonad m
  2. WP m ps
wp : {α : Type u}  m α  PredTrans ps α
Inherited from
  1. LawfulMonad m
  2. WP m ps
wp_pure :  {α : Type u} (a : α), wp (pure a) = pure a

WP.wp preserves pure.

wp_bind :  {α β : Type u} (x : m α) (f : α  m β),
  (wp do
      let a  x
      f a) =
    do
    let a  wp x
    wp (f a)

WP.wp preserves bind.

Missing WPMonad Instance

This reimplementation of Id has a WP instance, but no WPMonad instance:

def Identity (α : Type u) : Type u := α variable {α : Type u} def Identity.run (act : Identity α) : α := act instance : Monad Identity where pure x := x bind x f := f x instance : WP Identity .pure where wp x := PredTrans.pure x theorem Identity.of_wp_run_eq {x : α} {prog : Identity α} (h : Identity.run prog = x) (P : α Prop) : (⊢ₛ wp⟦prog ( a => P a)) P x := α:Type ux:αprog:Identity αh:prog.run = xP:α Prop(⊢ₛ wp⟦prog (PostCond.noThrow fun a => { down := P a })) P x α:Type ux:αprog:Identity αh:prog.run = xP:α Proph':⊢ₛ wp⟦prog (PostCond.noThrow fun a => { down := P a })P x All goals completed! 🐙

The missing instance prevents mvcgen from using its specifications for pure and bind. This tends to show up as a verification condition that's equal to the original goal. This function that reverses a list:

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

It is correct if it is equal to List.reverse. However, mvcgen does not make the goal easier to prove:

theorem rev_correct : (rev xs).run = xs.reverse := unsolved goals α✝:Type u_1xs x:List α✝h:(rev xs).run = xout✝:List α✝ := [](wp⟦do let r forIn xs out✝ fun x r => do pure PUnit.unit pure (ForInStep.yield (x :: r)) pure r (PostCond.noThrow fun a => { down := a = xs.reverse })).downα✝:Type u_1xs:List α✝(rev xs).run = xs.reverse α✝:Type u_1xs:List α✝x:List α✝h:(rev xs).run = xx = xs.reverse α✝:Type u_1xs:List α✝x:List α✝h:(rev xs).run = x⊢ₛ wp⟦rev xs (PostCond.noThrow fun a => { down := a = xs.reverse }) α✝:Type u_1xs:List α✝x:List α✝h:(rev xs).run = xout✝:List α✝ := [](wp⟦do let r forIn xs out✝ fun x r => do pure PUnit.unit pure (ForInStep.yield (x :: r)) pure r (PostCond.noThrow fun a => { down := a = xs.reverse })).down
unsolved goals
α✝:Type u_1xs x:List α✝h:(rev xs).run = xout✝:List α✝ := [](wp⟦do
      let r 
        forIn xs out✝ fun x r => do
            pure PUnit.unit
            pure (ForInStep.yield (x :: r))
      pure r
    (PostCond.noThrow fun a => { down := a = xs.reverse })).down

When the verification condition is just the original problem, without even any simplification of bind, the problem is usually a missing WPMonad instance. The issue can be resolved by adding a suitable instance:

instance : WPMonad Identity .pure where wp_pure _ := rfl wp_bind _ _ := rfl

With this instance, and a suitable invariant, mvcgen and grind can prove the theorem.

theorem rev_correct : (rev xs).run = xs.reverse := α✝:Type u_1xs:List α✝(rev xs).run = xs.reverse α✝:Type u_1xs:List α✝x:List α✝h:(rev xs).run = xx = xs.reverse α✝:Type u_1xs:List α✝x:List α✝h:(rev xs).run = x⊢ₛ wp⟦rev xs (PostCond.noThrow fun a => { down := a = xs.reverse }) α✝:Type u_1xs:List α✝x:List α✝h:(rev xs).run = x⊢ₛ wp⟦do let r forIn xs [] fun x r => do pure PUnit.unit pure (ForInStep.yield (x :: r)) pure r (PostCond.noThrow fun a => { down := a = xs.reverse }) mvcgen invariants · xs, out => out = xs.prefix.reverse with All goals completed! 🐙

18.2.3.3. Adequacy Lemmas🔗

Monads that can be invoked from pure code typically provide a 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. Adequacy 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.Do.Id.of_wp_run_eq.{u} {α : Type u} {x : α} {prog : Id α} (h : prog.run = x) (P : α Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a => { down := P a })) P x
Std.Do.Id.of_wp_run_eq.{u} {α : Type u} {x : α} {prog : Id α} (h : prog.run = x) (P : α Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a => { down := P a })) P x

Adequacy lemma for Id.run. Useful if you want to prove a property about an expression x defined as Id.run prog and you want to use mvcgen to reason about prog.

🔗theorem
Std.Do.StateM.of_wp_run_eq.{u_1} {σ : Type u_1} {s : σ} {α : Type u_1} {x : α × σ} {prog : StateM σ α} (h : StateT.run prog s = x) (P : α × σ Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a s' => P (a, s')) s) P x
Std.Do.StateM.of_wp_run_eq.{u_1} {σ : Type u_1} {s : σ} {α : Type u_1} {x : α × σ} {prog : StateM σ α} (h : StateT.run prog s = x) (P : α × σ Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a s' => P (a, s')) s) P x

Adequacy lemma for StateM.run. Useful if you want to prove a property about an expression x defined as StateM.run prog s and you want to use mvcgen to reason about prog.

🔗theorem
Std.Do.StateM.of_wp_run'_eq.{u_1} {σ : Type u_1} {s : σ} {α : Type u_1} {x : α} {prog : StateM σ α} (h : StateT.run' prog s = x) (P : α Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a => P a) s) P x
Std.Do.StateM.of_wp_run'_eq.{u_1} {σ : Type u_1} {s : σ} {α : Type u_1} {x : α} {prog : StateM σ α} (h : StateT.run' prog s = x) (P : α Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a => P a) s) P x

Adequacy lemma for StateM.run'. Useful if you want to prove a property about an expression x defined as StateM.run' prog s and you want to use mvcgen to reason about prog.

🔗theorem
Std.Do.ReaderM.of_wp_run_eq.{u_1} {ρ : Type u_1} {r : ρ} {α : Type u_1} {x : α} {prog : ReaderM ρ α} (h : ReaderT.run prog r = x) (P : α Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a x => P a) r) P x
Std.Do.ReaderM.of_wp_run_eq.{u_1} {ρ : Type u_1} {r : ρ} {α : Type u_1} {x : α} {prog : ReaderM ρ α} (h : ReaderT.run prog r = x) (P : α Prop) : (⊢ₛ wp⟦prog (PostCond.noThrow fun a x => P a) r) P x

Adequacy lemma for ReaderM.run. Useful if you want to prove a property about an expression x defined as ReaderM.run prog r and you want to use mvcgen to reason about prog.

🔗theorem
Std.Do.Except.of_wp {ε α : Type} {prog : Except ε α} (P : Except ε α Prop) : (⊢ₛ wp⟦prog (fun a => P (Except.ok a), fun e => P (Except.error e), ())) P prog
Std.Do.Except.of_wp {ε α : Type} {prog : Except ε α} (P : Except ε α Prop) : (⊢ₛ wp⟦prog (fun a => P (Except.ok a), fun e => P (Except.error e), ())) P prog

Adequacy lemma for Except. Useful if you want to prove a property about an expression prog : Except ε α and you want to use mvcgen to reason about prog.

🔗theorem
Std.Do.EStateM.of_wp_run_eq {ε σ : Type} {s : σ} {α : Type} {x : EStateM.Result ε σ α} {prog : EStateM ε σ α} (h : prog.run s = x) (P : EStateM.Result ε σ α Prop) : (⊢ₛ wp⟦prog (fun a s' => P (EStateM.Result.ok a s'), fun e s' => P (EStateM.Result.error e s'), ()) s) P x
Std.Do.EStateM.of_wp_run_eq {ε σ : Type} {s : σ} {α : Type} {x : EStateM.Result ε σ α} {prog : EStateM ε σ α} (h : prog.run s = x) (P : EStateM.Result ε σ α Prop) : (⊢ₛ wp⟦prog (fun a s' => P (EStateM.Result.ok a s'), fun e s' => P (EStateM.Result.error e s'), ()) s) P x

Adequacy lemma for EStateM.run. Useful if you want to prove a property about an expression x defined as EStateM.run prog s and you want to use mvcgen to reason about prog.

18.2.4. Hoare Triples

A Hoare triple (Hoare, 1969)C. A. R. Hoare (1969). “An Axiomatic Basis for Computer Programming”. Communications of the ACM.12 10pp. 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.

🔗def
Std.Do.Triple.{u, v} {m : Type u Type v} {ps : PostShape} [WP m ps] {α : Type u} (x : m α) (P : Assertion ps) (Q : PostCond α ps) : Prop
Std.Do.Triple.{u, v} {m : Type u Type v} {ps : PostShape} [WP m ps] {α : Type u} (x : m α) (P : Assertion ps) (Q : PostCond α ps) : Prop

A Hoare triple for reasoning about monadic programs. A proof for Triple x P Q is a specification for x: If assertion P holds before x, then postcondition Q holds after running x.

P x Q is convenient syntax for Triple x P Q.

syntaxHoare Triples
term ::= ...
    | A Hoare triple for reasoning about monadic programs.
A proof for `Triple x P Q` is a *specification* for `x`:
If assertion `P` holds before `x`, then postcondition `Q` holds after running `x`.

`⦃P⦄ x ⦃Q⦄` is convenient syntax for `Triple x P Q`.
 term  term  term 

P x Q is syntactic sugar for Triple x P Q.

🔗theorem
Std.Do.Triple.and.{u, v} {m : Type u Type v} {ps : PostShape} {α : Type u} {P₁ : Assertion ps} {Q₁ : PostCond α ps} {P₂ : Assertion ps} {Q₂ : PostCond α ps} [WP m ps] (x : m α) (h₁ : P₁ x Q₁) (h₂ : P₂ x Q₂) : P₁ P₂ x Q₁ ∧ₚ Q₂
Std.Do.Triple.and.{u, v} {m : Type u Type v} {ps : PostShape} {α : Type u} {P₁ : Assertion ps} {Q₁ : PostCond α ps} {P₂ : Assertion ps} {Q₂ : PostCond α ps} [WP m ps] (x : m α) (h₁ : P₁ x Q₁) (h₂ : P₂ x Q₂) : P₁ P₂ x Q₁ ∧ₚ Q₂

Conjunction for two Hoare triple specifications of a program x. This theorem is useful for decomposing proofs, because unrelated facts about x can be proven separately and then combined with this theorem.

🔗theorem
Std.Do.Triple.mp.{u, v} {m : Type u Type v} {ps : PostShape} {α : Type u} {P₁ : Assertion ps} {Q₁ : PostCond α ps} {P₂ : Assertion ps} {Q₂ : PostCond α ps} [WP m ps] (x : m α) (h₁ : P₁ x Q₁) (h₂ : P₂ x Q₁ →ₚ Q₂) : P₁ P₂ x Q₁ ∧ₚ Q₂
Std.Do.Triple.mp.{u, v} {m : Type u Type v} {ps : PostShape} {α : Type u} {P₁ : Assertion ps} {Q₁ : PostCond α ps} {P₂ : Assertion ps} {Q₂ : PostCond α ps} [WP m ps] (x : m α) (h₁ : P₁ x Q₁) (h₂ : P₂ x Q₁ →ₚ Q₂) : P₁ P₂ x Q₁ ∧ₚ Q₂

Modus ponens for two Hoare triple specifications of a program x. This theorem is useful for separating proofs. If h₁ : Triple x P₁ Q₁ proves a basic property about x and h₂ : Triple x P₂ (Q₁ →ₚ Q₂) is an advanced proof for Q₂ that builds on the basic proof for Q₁, then mp x h₁ h₂ is a proof for Q₂ about x.

18.2.5. Specification Lemmas

Specification lemmas are designated theorems that associate Hoare triples with functions. When mvcgen encounters a function, it checks whether there are any registered specification lemmas and attempts to use them to discharge intermediate verification conditions. If there is no applicable specification lemma, then the connection between the statement's pre- and postconditions will become a verification condition. Specification lemmas allow compositional reasoning about libraries of monadic code.

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. For theorems, the simpPre, simpPost, and specifiers are ignored.

The spec attribute may also be applied to definitions. On definitions, it indicates that the definition should be unfolded during verification condition generation. For definitions, spec uses the simpPre, simpPost, and specifiers in the same manner as simp.

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 (Use this rewrite rule before entering the subterms simpPre? | Use this rewrite rule after entering the subterms simpPost?) (? | <- ?) 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.

Universally-quantified variables in specification lemmas can be used to relate input states to output states and return values. These variables are referred to as schematic variables.

Schematic 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 schematic variable to stand for the initial state:

theorem double_spec : fun s => s = n double () s => s = 2 * n := n:Natfun s => s = n double PostCond.noThrow fun x s => s = 2 * n n:Natfun s => s = n modify fun x => 2 * x PostCond.noThrow fun x s => s = 2 * n mvcgen with All goals completed! 🐙

The assertion in the precondition is a function because the PostShape of StateM Nat is .arg Nat .pure, and Assertion (.arg Nat .pure) is SPred [Nat].

18.2.6. Invariant Specifications

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

🔗def
Std.Do.Invariant.{u₁, u₂} {α : Type u₁} (xs : List α) (β : Type u₂) (ps : PostShape) : Type (max u₂ u₁)
Std.Do.Invariant.{u₁, u₂} {α : Type u₁} (xs : List α) (β : Type u₂) (ps : PostShape) : Type (max u₂ u₁)

The type of loop invariants used by the specifications of for ... in ... loops. A loop invariant is a PostCond that takes as parameters

  • A List.Cursor xs representing the iteration state of the loop. It is parameterized by the list of elements xs that the for loop iterates over.

  • A state tuple of type β, which will be a nesting of MProds representing the elaboration of let mut variables and early return.

The loop specification lemmas will use this in the following way: Before entering the loop, the cursor's prefix is empty and the suffix is xs. After leaving the loop, the cursor's prefix is xs and the suffix is empty. During the induction step, the invariant holds for a suffix with head element x. After running the loop body, the invariant then holds after shifting x to the prefix.

🔗def
Std.Do.Invariant.withEarlyReturn.{u₁, u₂} {β : Type (max u₁ u₂)} {ps : PostShape} {α✝ : Type (max u₁ u₂)} {xs : List α✝} {γ : Type (max u₁ u₂)} (onContinue : xs.Cursor β Assertion ps) (onReturn : γ β Assertion ps) (onExcept : ExceptConds ps := ExceptConds.false) : Invariant xs (MProd (Option γ) β) ps
Std.Do.Invariant.withEarlyReturn.{u₁, u₂} {β : Type (max u₁ u₂)} {ps : PostShape} {α✝ : Type (max u₁ u₂)} {xs : List α✝} {γ : Type (max u₁ u₂)} (onContinue : xs.Cursor β Assertion ps) (onReturn : γ β Assertion ps) (onExcept : ExceptConds ps := ExceptConds.false) : Invariant xs (MProd (Option γ) β) ps

Helper definition for specifying loop invariants for loops with early return.

for ... in ... loops with early return of type γ elaborate to a call like this:

forIn (β := MProd (Option γ) ...) (b := ⟨none, ...⟩) collection loopBody

Note that the first component of the MProd state tuple is the optional early return value. It is none as long as there was no early return and some r if the loop returned early with r.

This function allows to specify different invariants for the loop body depending on whether the loop terminated early or not. When there was an early return, the loop has effectively finished, which is encoded by the additional xs.suffix = [] assertion in the invariant. This assertion is vital for successfully proving the induction step, as it contradicts with the assumption that xs.suffix = x::rest of the inductive hypothesis at the start of the loop body, meaning that users won't need to prove anything about the bogus case where the loop has returned early yet takes another iteration of the loop body.

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 of 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. The current position in the loop is tracked with a List.Cursor that represents a position in a list as a combination of the elements to the left of the position and the elements to the right. This type is not a traditional zipper, in which the prefix is reversed for efficient movement: it is intended for use in specifications and proofs, not in run-time code, so the prefix is in the original order.

🔗structure
List.Cursor.{u} {α : Type u} (l : List α) : Type u
List.Cursor.{u} {α : Type u} (l : List α) : Type u

Constructor

List.Cursor.mk.{u}

Fields

prefix : List α
suffix : List α
property : self.prefix ++ self.suffix = l
🔗def
List.Cursor.at.{u_1} {α : Type u_1} (l : List α) (n : Nat) : l.Cursor
List.Cursor.at.{u_1} {α : Type u_1} (l : List α) (n : Nat) : l.Cursor
🔗def
List.Cursor.pos.{u_1} {α✝ : Type u_1} {l : List α✝} (c : l.Cursor) : Nat
List.Cursor.pos.{u_1} {α✝ : Type u_1} {l : List α✝} (c : l.Cursor) : Nat

The position of the cursor in the list. It's a shortcut for the number of elements in the prefix.

🔗def
List.Cursor.current.{u_1} {α : Type u_1} {l : List α} (c : l.Cursor) (h : 0 < c.suffix.length := by get_elem_tactic) : α
List.Cursor.current.{u_1} {α : Type u_1} {l : List α} (c : l.Cursor) (h : 0 < c.suffix.length := by get_elem_tactic) : α
🔗def
List.Cursor.tail.{u_1} {α✝ : Type u_1} {l : List α✝} (s : l.Cursor) (h : 0 < s.suffix.length := by get_elem_tactic) : l.Cursor
List.Cursor.tail.{u_1} {α✝ : Type u_1} {l : List α✝} (s : l.Cursor) (h : 0 < s.suffix.length := by get_elem_tactic) : l.Cursor
🔗def
List.Cursor.begin.{u_1} {α : Type u_1} (l : List α) : l.Cursor
List.Cursor.begin.{u_1} {α : Type u_1} (l : List α) : l.Cursor
🔗def
List.Cursor.end.{u_1} {α : Type u_1} (l : List α) : l.Cursor
List.Cursor.end.{u_1} {α : Type u_1} (l : List α) : l.Cursor