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 over states, where each state is defined by a list of component state types.

Example:

SPred [Nat, Bool] = (Nat Bool ULift Prop)

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 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.

One predicate P entails another predicate Q if Q is true in every state in which P is true. Unlike implication (SPred.imp), entailment is not itself an SPred, but is instead an ordinary proposition.

🔗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

Logical equivalence of SPred.

Logically equivalent predicates are equal. Use SPred.bientails.to_eq to convert bi-entailment to equality.

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(_)» : termAn embedding of the special syntax for `SPred` into ordinary terms that provides alternative interpretations of logical connectives and quantifiers. Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax. spred. The usual meanings of these operators can be recovered by using the Std.Do.«termTerm(_)» : termEscapes from a surrounding `spred(...)` term, returning to the usual interpretations of quantifiers and connectives. term operator.

syntaxPredicate Terms

Std.Do.«termSpred(_)» : termAn embedding of the special syntax for `SPred` into ordinary terms that provides alternative interpretations of logical connectives and quantifiers. Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax. spred indicates that logical connectives and quantifiers should be understood as those pertaining to stateful predicates, while Std.Do.«termTerm(_)» : termEscapes from a surrounding `spred(...)` term, returning to the usual interpretations of quantifiers and connectives. term indicates that they should have the usual meaning.

term ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
spred(term)
term ::= ...
    | Escapes from a surrounding `spred(...)` term, returning to the usual interpretations of quantifiers
and connectives.
term(term)

18.2.1.3. Connectives and Quantifiers

syntaxPredicate Connectives
term ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
spred(term  term)

Syntactic sugar for SPred.imp.

term ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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: states that satisfy P and satisfy Q satisfy spred(P Q).

🔗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 stateful predicates. A state satisfies conjunction env if it satisfies all predicates in env.

🔗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: states that either satisfy P or satisfy Q satisfy spred(P Q).

🔗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: states that do not satisfy P satisfy spred(¬ P).

🔗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: states that satisfy Q whenever they satisfy P satisfy spred(P Q).

🔗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

Biimplication in SPred: states that either satisfy both P and Q or satisfy neither satisfy spred(P Q).

syntaxPredicate Quantifiers
term ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
spred( ident, term)
term ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
spred( ident : term,  term)
term ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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 ::= ...
    | An embedding of the special syntax for `SPred` into ordinary terms that provides alternative
interpretations of logical connectives and quantifiers.

Within `spred(...)`, `term(...)` escapes to the ordinary Lean interpretation of this syntax.
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:

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 σ

Gets 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 α

Curries 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 α

Uncurries 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)

The “shape” of the postconditions that are used to reason about a monad.

A postcondition shape is an abstraction of many possible monadic effects, based on the structure of pure functions that can simulate them. The postcondition shape of a monad is given by its WP instance. This shape is used to determine both its Assertions and its PostConds.

Constructors

pure.{u} : PostShape

The assertions and postconditions in this monad use neither state nor exceptions.

arg.{u} (σ : Type u) : PostShape  PostShape

The assertions in this monad may mention the current value of a state of type σ, and postconditions may mention the state's final value.

except.{u} (ε : Type u) : PostShape  PostShape

The postconditions in this monad include assertions about exceptional values of type ε that result from premature termination.

🔗def
Std.Do.PostShape.args.{u} : PostShape List (Type u)
Std.Do.PostShape.args.{u} : PostShape List (Type u)

Extracts the list of state types under PostShape.arg constructors, discarding exception types.

The state types determine the shape of assertions in the monad.

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

An assertion about the state fields for a monad whose postcondition shape is ps.

Concretely, this is an abbreviation for SPred applied to the .args in the given predicate shape, so all theorems about SPred apply.

Examples:

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
🔗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

An assertion about each potential exception that's declared in a postcondition shape.

Examples:

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 ::= ...
    | Entailment of postconditions.

This consists of:
 * Entailment of the assertion about the return value, for all possible return values.
 * Entailment of the exception conditions.

While implication of postconditions (`PostCond.imp`) results in a new postcondition, entailment is
an ordinary proposition.
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

Entailment of postconditions.

This consists of:

  • Entailment of the assertion about the return value, for all possible return values.

  • Entailment of the exception conditions.

While implication of postconditions (PostCond.imp) results in a new postcondition, entailment is an ordinary proposition.

syntaxPostcondition Conjunction
term ::= ...
    | Conjunction of postconditions.

This is defined pointwise, as the conjunction of the assertions about the return value and the
conjunctions of the assertions about each potential exception.
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

Conjunction of postconditions.

This is defined pointwise, as the conjunction of the assertions about the return value and the conjunctions of the assertions about each potential exception.

syntaxPostcondition Implication
term ::= ...
    | Implication of postconditions.

This is defined pointwise, as the implication of the assertions about the return value and the
implications of each of the assertions about each potential exception.

While entailment of postconditions (`PostCond.entails`) is an ordinary proposition, implication of
postconditions is itself a postcondition.
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

Implication of postconditions.

This is defined pointwise, as the implication of the assertions about the return value and the implications of each of the assertions about each potential exception.

While entailment of postconditions (PostCond.entails) is an ordinary proposition, implication of postconditions is itself a postcondition.

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 α

The identity predicate transformer that transforms the postcondition's assertion about the return value into an assertion about a.

🔗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 β

Sequences two predicate transformers by composing them.

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) α

Adds the ability to make assertions about a state of type σ to a predicate transformer with postcondition shape ps, resulting in postcondition shape .arg σ ps. This is done by interpreting StateT σ (PredTrans ps) α into PredTrans (.arg σ ps) α.

This can be used to for all kinds of state-like effects, including reader effects or append-only states, by interpreting them as states.

🔗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) α

Adds the ability to make assertions about exceptions of type ε to a predicate transformer with postcondition shape ps, resulting in postcondition shape .except ε ps. This is done by interpreting ExceptT ε (PredTrans ps) α into PredTrans (.except ε ps) α.

This can be used for all kinds of exception-like effects, such as early termination, by interpreting them as exceptions.

🔗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) α

Adds the ability to make assertions about early termination to a predicate transformer with postcondition shape ps, resulting in postcondition shape .except PUnit ps. This is done by interpreting OptionT (PredTrans ps) α into PredTrans (.except PUnit ps) α, which models the type Option as being equivalent to Except PUnit.

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.

For practical reasoning, an instance of WPMonad m ps is typically needed in addition to WP m ps.

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 monad with weakest preconditions (WP) that is also a monad morphism, preserving pure and bind.

In practice, mvcgen is not useful for reasoning about programs in a monad that is without a WPMonad instance. The specification lemmas for Pure.pure and Bind.bind, as well as those for operators like Functor.map, require that their monad have a WPMonad instance.

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 Hoare triple 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 Hoare triple `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

A pointer at a specific location in a list. List cursors are used in loop invariants for the mvcgen tactic.

Moving the cursor to the left or right takes time linear in the current position of the cursor, so this data structure is not appropriate for run-time code.

Constructor

List.Cursor.mk.{u}

Fields

prefix : List α

The elements before to the current position in the list.

suffix : List α

The elements starting at the current position. If the position is after the last element of the list, then the suffix is empty; otherwise, the first element of the suffix is the current element that the cursor points to.

property : self.prefix ++ self.suffix = l

Appending the prefix to the suffix yields the original list.

🔗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

Creates a cursor at position n in the list l. The prefix contains the first n elements, and the suffix contains the remaining elements. If n is larger than the length of the list, the cursor is positioned at the end of the list.

🔗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) : α

Returns the element at the current cursor position.

Requires that is a current element: the suffix must be non-empty, so the cursor is not at the end of the list.

🔗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

Advances the cursor by one position, moving the current element from the suffix to the prefix.

Requires that the cursor is not already at the end of the list.

🔗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

Creates a cursor at the beginning of the list (position 0). The prefix is empty and the suffix is the entire list.

🔗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

Creates a cursor at the end of the list. The prefix is the entire list and the suffix is empty.