A predicate over states, where each state is defined by a list of component state types.
Example:
SPred [Nat, Bool] = (Nat → Bool → ULift Prop)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.
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.
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.
SPredterm ::= ...
| 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.
The predicate ItIsSecret expresses that a state of type String is "secret":
def ItIsSecret : SPred [String] := fun s => ⌜s = "secret"⌝
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).
Logical equivalence of SPred.
Logically equivalent predicates are equal. Use SPred.bientails.to_eq to convert bi-entailment to
equality.
SPredterm ::= ...
| 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 σ.
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.
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)term ::= ... |spred(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.term ∧ term)`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`.
Syntactic sugar for SPred.and.
term ::= ... |spred(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.term ∨ term)`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`.
Syntactic sugar for SPred.or.
term ::= ... |spred(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.¬ term)`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`.
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 ::= ... |spred(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.term ↔ term)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`.
Syntactic sugar for SPred.iff.
Conjunction of a list of stateful predicates. A state satisfies conjunction env if it satisfies
all predicates in env.
Biimplication in SPred: states that either satisfy both P and Q or satisfy neither satisfy
spred(P ↔ Q).
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 ::= ... |spred(∀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.(ident (ident |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)`.hole)* : term), term)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 ::= ... |spred(∀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._, term)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 ::= ... |spred(∀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._ : term, term)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 ::= ... |spred(∀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.(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 |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)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).
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(∃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.ident, term)`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.
term ::= ... |spred(∃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.ident : term, term)`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.
term ::= ... |spred(∃ (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.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)`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.
term ::= ... |spred(∃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.`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._, term)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 ::= ... |spred(∃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.`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._ : term, term)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 ::= ... |spred(∃ (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.`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* : term), term)`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.
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.
Just as SPred represents predicate over states, SVal represents a value that is derived from a state.
A value indexed by a curried tuple of states.
Example:
example : SVal [Nat, Bool] String = (Nat → Bool → String) := rfl
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.
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.
pure.{u} : PostShape
The assertions and postconditions in this monad use neither state nor exceptions.
Extracts the list of state types under PostShape.arg constructors, discarding exception types.
The state types determine the shape of assertions in the monad.
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
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
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.
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.
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.
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 ::= ...
| 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.
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 ::= ...
| 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
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 ::= ...
| 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
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 ::= ...
| 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
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.
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.
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.
Std.Do.PredTrans.mk.{u}
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.
Transforming a conjunction of postconditions is the same as the conjunction of transformed postconditions.
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.
The identity predicate transformer that transforms the postcondition's assertion about the return
value into an assertion about a.
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.
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.
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.
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.
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.
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.
Std.Do.WP.mk.{u, v}
wp : {α : Type u} → m α → PredTrans ps α
Interpret a monadic program x : m α in terms of a predicate transformer PredTrans ps α.
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.
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.
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.
Std.Do.WPMonad.mk.{u, v}
map_const : ∀ {α β : Type u}, Functor.mapConst = Functor.map ∘ Function.const β
LawfulMonad mWP m psid_map : ∀ {α : Type u} (x : m α), id <$> x = x
LawfulMonad mWP m pscomp_map : ∀ {α β γ : Type u} (g : α → β) (h : β → γ) (x : m α), (h ∘ g) <$> x = h <$> g <$> x
LawfulMonad mWP m psseqLeft_eq : ∀ {α β : Type u} (x : m α) (y : m β), x <* y = Function.const β <$> x <*> y
LawfulMonad mWP m psseqRight_eq : ∀ {α β : Type u} (x : m α) (y : m β), x *> y = Function.const α id <$> x <*> y
LawfulMonad mWP m pspure_seq : ∀ {α β : Type u} (g : α → β) (x : m α), pure g <*> x = g <$> x
LawfulMonad mWP m psmap_pure : ∀ {α β : Type u} (g : α → β) (x : α), g <$> pure x = pure (g x)
LawfulMonad mWP m psseq_pure : ∀ {α β : Type u} (g : m (α → β)) (x : α), g <*> pure x = (fun h => h x) <$> g
LawfulMonad mWP m psseq_assoc : ∀ {α β γ : Type u} (x : m α) (g : m (α → β)) (h : m (β → γ)), h <*> (g <*> x) = Function.comp <$> h <*> g <*> x
LawfulMonad mWP m psbind_pure_comp : ∀ {α β : Type u} (f : α → β) (x : m α), (do let a ← x pure (f a)) = f <$> x
LawfulMonad mWP m psbind_map : ∀ {α β : Type u} (f : m (α → β)) (x : m α), (do let x_1 ← f x_1 <$> x) = f <*> x
LawfulMonad mWP m pspure_bind : ∀ {α β : Type u} (x : α) (f : α → m β), pure x >>= f = f x
LawfulMonad mWP m psbind_assoc : ∀ {α β γ : Type u} (x : m α) (f : α → m β) (g : β → m γ), x >>= f >>= g = x >>= fun x => f x >>= g
LawfulMonad mWP m pswp : {α : Type u} → m α → PredTrans ps α
LawfulMonad mWP m pswp_pure : ∀ {α : Type u} (a : α), wp (pure a) = pure a
wp_bind : ∀ {α β : Type u} (x : m α) (f : α → m β), (wp do let a ← x f a) = do let a ← wp x wp (f a)
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 := α✝:Type u_1xs:List α✝⊢ (rev xs).run = xs.reverse
α✝:Type u_1xs:List α✝x:List α✝h:(rev xs).run = x⊢ x = 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
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 = x⊢ x = 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! 🐙
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.
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.
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.
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.
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.
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.
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.
attr ::= ... |spec (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.simpPre? |Use this rewrite rule before entering the subtermssimpPost?) (← ? | <- ?) prio?Use this rewrite rule after entering the subterms
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.
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:Nat⊢ ⦃fun s => ⌜s = n⌝⦄ double ⦃PostCond.noThrow fun x s => ⌜s = 2 * n⌝⦄
n:Nat⊢ ⦃fun 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].
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.
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.
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 γ) β) psStd.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.
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.
List.Cursor.mk.{u}
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.
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.
The position of the cursor in the list. It's a shortcut for the number of elements in the prefix.
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.
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.
Creates a cursor at the beginning of the list (position 0). The prefix is empty and the suffix is the entire list.