Lean 语言参考

17.2. 谓词变压器🔗

谓词转换器语义是将程序解释为从谓词到谓词的函数,而不是从值到值的函数。 postcondition 是在运行程序后成立的断言,而 precondition 是在运行程序之前必须成立的断言,以便保证后置条件成立。

mvcgen 使用的谓词转换器语义将后置条件转换为 最弱的前提条件,在此情况下程序将确保后置条件。 如果在所有状态下 P' 足以证明 P,但 P 不足以证明 P',则断言 P 弱于 P'。 逻辑上等价的断言被认为是相等的。

所讨论的谓词是有状态的:它们可以提及程序的当前状态。 此外,后置条件可以将返回值和程序抛出的任何异常与最终状态相关联。 SPred 是一种在单子状态上参数化的谓词,表示为构成状态的字段类型列表。 通常的逻辑连接词和量词是为 SPred 定义的。 可与 mvcgen 一起使用的每个 monad 都由 WP 的实例分配一个状态类型,而 Assertion 是该 monad 的对应断言类型,用于前提条件。 AssertionSPred 的包装器:SPred 由状态类型列表参数化,而 Assertion 由信息更丰富的类型参数化,该类型将转换为 SPred 的状态类型列表。 PostCond 将有关返回值的 Assertion 与有关潜在异常的断言配对;可用的异常也由 monad 的 WP 实例指定。

17.2.1. 状态谓词🔗

一元程序的谓词变换器语义基于命题可能提及程序状态的逻辑。 这里,“状态”不仅指可变状态,还指只读值,例如通过 ReaderT 提供的值。 不同的 monad 有不同的可用状态类型,但每个单独的状态总是有一个类型。 给定状态类型列表,SPred 是这些状态的一种谓词。

SPred 本质上并不与单子验证框架相关。 相关的 Assertion 为通过其 WP 实例的 PostShape 输出参数表示的 monad 状态计算合适的 SPred

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

不提及状态的普通命题可以通过添加一个简单的全称量化来用作有状态谓词。 这是用语法 P 编写的,它是 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

谓词 ItIsSecret 表示 String 类型的状态是 "secret"

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

17.2.1.1. 蕴涵🔗

有状态谓词通过entailment相关。 有状态谓词的蕴含被定义为全称量化蕴涵:如果 PQ 是状态 \sigma 上的谓词,则当 ∀ s : \sigma, P(s) → Q(s) 时,P 蕴含 Q(写作 P \vdash_s Q)。

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

有状态谓词的逻辑包括蕴涵连接词。 蕴涵和蕴涵之间的区别在于蕴涵是 Lean 逻辑中的语句,而蕴涵是状态逻辑的内部。 给定状态 σ 的状态谓词 PQP ⊢ₛ QProp,而 spred(P Q)SPred σ

17.2.1.2. 符号🔗

有状态谓词的语法与普通 Lean 术语的语法重叠。 特别是,有状态谓词使用逻辑连接词和量词的常用语法。 与有状态谓词相关的语法在上下文中自动启用,例如明确意图的前置条件和后置条件;其他上下文必须显式选择使用 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 的语法。 可以使用 Std.Do.«termTerm(_)» : termEscapes from a surrounding `spred(...)` term, returning to the usual interpretations of quantifiers and connectives. term 运算符恢复这些运算符的通常含义。

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 表示逻辑连接词和量词应被理解为与状态谓词相关的连接词和量词,而 Std.Do.«termTerm(_)» : termEscapes from a surrounding `spred(...)` term, returning to the usual interpretations of quantifiers and connectives. 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(term)
term ::= ...
    | Escapes from a surrounding `spred(...)` term, returning to the usual interpretations of quantifiers
and connectives.
term(term)

17.2.1.3. 连接词和量词🔗

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)

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)

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)

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)

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)

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)

全称量化的每种形式都是在以量化变量作为参数的函数上调用 SPred.forall 的语法糖。

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)

存在量化的每种形式都是在以量化变量作为参数的函数上调用 SPred.exists 的语法糖。

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

17.2.1.4. 有状态的值🔗

正如 SPred 表示状态上的谓词一样,SVal 表示从状态派生的值。

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

17.2.2. 断言🔗

关于单子程序的断言语言由 postcondition shape 参数化,它描述了给定单子中计算的输入和输出。 前置条件可能会提到 monad 状态的初始值,而后置条件可能会提到返回值、monad 状态的最终值,并且还必须考虑可能引发的任何异常。 给定 monad 的后置条件形状决定了 monad 中的状态和异常。 PostShape.pure 描述一个单子,其中断言可能不提及任何状态,PostShape.arg 描述状态值,PostShape.except 描述可能的异常。 因为这些构造函数可以不断添加,所以可以根据底层转换后的单子的后置条件形状来定义单子变换器的后置条件形状。 在幕后,通过将后置条件形状转换为状态类型列表并丢弃异常,将 Assertion 转换为适当的 SPred

🔗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

Std.Do.PostShape.pure.{u} : PostShape

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

Std.Do.PostShape.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.

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

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

🔗def

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

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

产品构造函数的嵌套序列的语法糖,以 () 终止,其中第一个元素是关于非异常返回值的断言,其余元素是关于后置条件的异常情况的断言。

🔗def

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

可能引发异常的程序的后置条件有两种。 总正确性解释 P prog r => Q' r 断言,如果 P 成立,则 prog 终止并且 Q' 对于结果成立。 部分正确性解释 P prog ⇓? r => Q' r 断言,给定 P 成立,并且if prog 终止then Q' 对于结果成立。

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_1} {α : Type u_1} {ps : PostShape} (p : α Assertion ps) : PostCond α ps
Std.Do.PostCond.noThrow.{u_1} {α : Type u_1} {ps : PostShape} (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_1} {α : Type u_1} {ps : PostShape} (p : α Assertion ps) : PostCond α ps
Std.Do.PostCond.mayThrow.{u_1} {α : Type u_1} {ps : PostShape} (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

PostCond.entails 的语法糖

🔗def
Std.Do.PostCond.entails.{u_1} {α : Type u_1} {ps : PostShape} (p q : PostCond α ps) : Prop
Std.Do.PostCond.entails.{u_1} {α : Type u_1} {ps : PostShape} (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

PostCond.and 的语法糖

🔗def
Std.Do.PostCond.and.{u_1} {α : Type u_1} {ps : PostShape} (p q : PostCond α ps) : PostCond α ps
Std.Do.PostCond.and.{u_1} {α : Type u_1} {ps : PostShape} (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

PostCond.imp 的语法糖

🔗def
Std.Do.PostCond.imp.{u_1} {α : Type u_1} {ps : PostShape} (p q : PostCond α ps) : PostCond α ps
Std.Do.PostCond.imp.{u_1} {α : Type u_1} {ps : PostShape} (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.

17.2.3. 谓词变压器🔗

谓词变换器是从某些后置条件状态的后置条件到该状态的断言的函数。 该函数必须是 conjunctive,这意味着它必须分布在 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

trans : PostCond α ps  Assertion ps

The function implementing the predicate transformer.

conjunctiveRaw : PredTrans.Conjunctive self.trans

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.

谓词转换器形成一个 monad。 pure 运算符是身份转换器;它只是用它的参数实例化后置条件。 bind 运算符组成谓词转换器。

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

辅助运算符 PredTrans.pushArgPredTrans.pushExceptPredTrans.pushOption 通过添加标准副作用来修改谓词转换器。 它们用于实现 StateTExceptTOptionT 等变压器的 WP 实例;它们还可以用于实现可以根据其中之一来考虑的 monad。 例如,PredTrans.pushArg 通常用于状态 monad,但也可用于实现读取器 monad 的实例,将读取器的值视为只读状态。

🔗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

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.

17.2.3.1. 最弱的先决条件🔗

monad 的 weakest precondition 语义由 WP 类型类提供。 WP 的实例确定 monad 的后置条件形状,并提供将 monad 的操作解释为其后置条件形状中的谓词变换器的逻辑规则。

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

17.2.3.2. 最弱先决条件单子态射🔗

除了 WP 实例之外,mvcgen 的大多数内置规范引理还依赖于 WPMonad 实例的存在。 除了合法之外,单子实现 purebind 的最弱前提条件还应该对应于谓词变换单子的 purebind 运算符。 如果没有 WPMonad 实例,mvcgen 通常会返回原始证明目标不变。

🔗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

Id 的重新实现有一个 WP 实例,但没有 WPMonad 实例:

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 All goals completed! 🐙

缺少的实例会阻止 mvcgen 使用其 purebind 的规范。 这往往表现为等于原始目标的验证条件。 该函数反转列表:

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

如果等于List.reverse则正确。 然而,mvcgen 并没有让目标更容易证明:

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

当验证条件只是原始问题时,甚至没有对 bind 进行任何简化,问题通常是缺少 WPMonad 实例。 该问题可以通过添加合适的实例来解决:

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

通过这个实例,以及合适的不变量,mvcgengrind 可以证明该定理。

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! 🐙

17.2.3.3. 充分性引理🔗

可以从纯代码调用的 Monad 通常提供一个调用运算符,该运算符将任何所需的输入状态作为参数,并返回与输出状态配对的值或某种异常值。 示例包括 StateT.runExceptT.runId.runAdequacy lemmas 在有关单子程序调用的语句与这些程序的 最弱前提条件 语义(由其 WP 实例给出)之间提供桥梁。 它们表明,如果最弱的前提条件为真,则有关调用的属性为真。

🔗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_eq.{u} {ε α : Type u} {x prog : Except ε α} (h : prog = x) (P : Except ε α Prop) : (⊢ₛ wp⟦prog (fun a => P (Except.ok a), fun e => P (Except.error e), PUnit.unit)) P x
Std.Do.Except.of_wp_eq.{u} {ε α : Type u} {x prog : Except ε α} (h : prog = x) (P : Except ε α Prop) : (⊢ₛ wp⟦prog (fun a => P (Except.ok a), fun e => P (Except.error e), PUnit.unit)) P x

Adequacy lemma for Except. Useful if you want to prove a property about a complex expression prog : Except ε α that you have generalized to a variable x and you want to use mvcgen to reason about prog.

🔗theorem
Std.Do.EStateM.of_wp_run_eq.{u_1} {ε σ : Type u_1} {s : σ} {α : Type u_1} {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'), PUnit.unit) s) P x
Std.Do.EStateM.of_wp_run_eq.{u_1} {ε σ : Type u_1} {s : σ} {α : Type u_1} {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'), PUnit.unit) 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.

17.2.4. 霍尔三元组🔗

Hoare Triple (Hoare, 1969)C. A. R. Hoare (1969). “An Axiomatic Basis for Computer Programming”. Communications of the ACM. 12(10), pp. 576–583. 由前置条件、程序和后置条件组成。 在前置条件为 true 的状态下运行程序会导致后置条件为 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 QTriple 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.

17.2.5. 规范引理🔗

规范引理是将霍尔三元组与函数关联起来的指定定理。 当 mvcgen 遇到函数时,它会检查是否有任何已注册的规范引理,并尝试使用它们来释放中间 验证条件。 如果没有适用的规范引理,则语句的前置条件和后置条件之间的连接将成为验证条件。 规范引理允许对一元代码库进行组合推理。

当应用于其陈述为 Hoare 三元组的定理时,spec 属性将该定理注册为规范引理。 这些引理按优先级顺序使用。

spec 属性也可以应用于定义。 在定义上,表示验证条件生成时应展开定义。

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

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

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

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

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

规范引理中的全称量化变量可用于将输入状态与输出状态和返回值相关联。 这些变量称为 原理图变量

Schematic Variables

函数 doubleNat 状态的值加倍:

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

它的规范应该与初始状态和最终状态相关,但它无法知道它们的精确值。 该规范使用一个示意性变量来代表初始状态:

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! 🐙

前提条件中的断言是一个函数,因为 StateM NatPostShape.arg Nat .pure,并且 Assertion (.arg Nat .pure)SPred [Nat]

17.2.6. 规格不变🔗

这些类型用于不变量。 ForIn.forInForIn'.forIn'规范引理 采用 Invariant 类型的参数,并且 mvcgen 确保其他自动化不会意外生成不变量。

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

不变量使用列表对 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 循环中的值序列进行建模。 循环中的当前位置通过 List.Cursor 进行跟踪,该 List.Cursor 将列表中的位置表示为位置左侧元素和右侧元素的组合。 这种类型不是传统的拉链,其中前缀是相反的以实现高效移动:它旨在用于规范和证明,而不是运行时代码,因此前缀按原始顺序排列。

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