A predicate over states, where each state is defined by a list of component state types.
Example:
SPred [Nat, Bool] = (Nat → Bool → ULift Prop)谓词转换器语义是将程序解释为从谓词到谓词的函数,而不是从值到值的函数。 postcondition 是在运行程序后成立的断言,而 precondition 是在运行程序之前必须成立的断言,以便保证后置条件成立。
mvcgen 使用的谓词转换器语义将后置条件转换为 最弱的前提条件,在此情况下程序将确保后置条件。
如果在所有状态下 P' 足以证明 P,但 P 不足以证明 P',则断言 P 弱于 P'。
逻辑上等价的断言被认为是相等的。
所讨论的谓词是有状态的:它们可以提及程序的当前状态。
此外,后置条件可以将返回值和程序抛出的任何异常与最终状态相关联。
SPred 是一种在单子状态上参数化的谓词,表示为构成状态的字段类型列表。
通常的逻辑连接词和量词是为 SPred 定义的。
可与 mvcgen 一起使用的每个 monad 都由 WP 的实例分配一个状态类型,而 Assertion 是该 monad 的对应断言类型,用于前提条件。
Assertion 是 SPred 的包装器:SPred 由状态类型列表参数化,而 Assertion 由信息更丰富的类型参数化,该类型将转换为 SPred 的状态类型列表。
PostCond 将有关返回值的 Assertion 与有关潜在异常的断言配对;可用的异常也由 monad 的 WP 实例指定。
一元程序的谓词变换器语义基于命题可能提及程序状态的逻辑。
这里,“状态”不仅指可变状态,还指只读值,例如通过 ReaderT 提供的值。
不同的 monad 有不同的可用状态类型,但每个单独的状态总是有一个类型。
给定状态类型列表,SPred 是这些状态的一种谓词。
SPred 本质上并不与单子验证框架相关。
相关的 Assertion 为通过其 WP 实例的 PostShape 输出参数表示的 monad 状态计算合适的 SPred。
不提及状态的普通命题可以通过添加一个简单的全称量化来用作有状态谓词。
这是用语法 ⌜P⌝ 编写的,它是 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.
谓词 ItIsSecret 表示 String 类型的状态是 "secret":
def ItIsSecret : SPred [String] := fun s => ⌜s = "secret"⌝
有状态谓词通过entailment相关。
有状态谓词的蕴含被定义为全称量化蕴涵:如果 P 和 Q 是状态 \sigma 上的谓词,则当 ∀ s : \sigma, P(s) → Q(s) 时,P 蕴含 Q(写作 P \vdash_s Q)。
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.
有状态谓词的逻辑包括蕴涵连接词。
蕴涵和蕴涵之间的区别在于蕴涵是 Lean 逻辑中的语句,而蕴涵是状态逻辑的内部。
给定状态 σ 的状态谓词 P 和 Q,P ⊢ₛ Q 是 Prop,而 spred(P → Q) 是 SPred σ。
有状态谓词的语法与普通 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 运算符恢复这些运算符的通常含义。
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)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`.
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`.
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`.
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 ::= ... |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`.
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).
全称量化的每种形式都是在以量化变量作为参数的函数上调用 SPred.forall 的语法糖。
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.
存在量化的每种形式都是在以量化变量作为参数的函数上调用 SPred.exists 的语法糖。
正如 SPred 表示状态上的谓词一样,SVal 表示从状态派生的值。
A value indexed by a curried tuple of states.
Example:
example : SVal [Nat, Bool] String = (Nat → Bool → String) := rfl
关于单子程序的断言语言由 postcondition shape 参数化,它描述了给定单子中计算的输入和输出。
前置条件可能会提到 monad 状态的初始值,而后置条件可能会提到返回值、monad 状态的最终值,并且还必须考虑可能引发的任何异常。
给定 monad 的后置条件形状决定了 monad 中的状态和异常。
PostShape.pure 描述一个单子,其中断言可能不提及任何状态,PostShape.arg 描述状态值,PostShape.except 描述可能的异常。
因为这些构造函数可以不断添加,所以可以根据底层转换后的单子的后置条件形状来定义单子变换器的后置条件形状。
在幕后,通过将后置条件形状转换为状态类型列表并丢弃异常,将 Assertion 转换为适当的 SPred。
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.
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.
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
产品构造函数的嵌套序列的语法糖,以 () 终止,其中第一个元素是关于非异常返回值的断言,其余元素是关于后置条件的异常情况的断言。
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' 对于结果成立。
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.
Std.Do.PostCond.noThrow.{u_1} {α : Type u_1} {ps : PostShape} (p : α → Assertion ps) : PostCond α psStd.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.
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.
Std.Do.PostCond.mayThrow.{u_1} {α : Type u_1} {ps : PostShape} (p : α → Assertion ps) : PostCond α psStd.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.
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 的语法糖
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
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
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.
谓词变换器是从某些后置条件状态的后置条件到该状态的断言的函数。
该函数必须是 conjunctive,这意味着它必须分布在 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}
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.
Std.Do.PredTrans.Conjunctive.{u} {ps : PostShape} {α : Type u} (t : PostCond α ps → Assertion ps) : PropStd.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.
Std.Do.PredTrans.Monotonic.{u} {ps : PostShape} {α : Type u} (t : PostCond α ps → Assertion ps) : PropStd.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 运算符组成谓词转换器。
The identity predicate transformer that transforms the postcondition's assertion about the return
value into an assertion about a.
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.pushArg、PredTrans.pushExcept 和 PredTrans.pushOption 通过添加标准副作用来修改谓词转换器。
它们用于实现 StateT、ExceptT 和 OptionT 等变压器的 WP 实例;它们还可以用于实现可以根据其中之一来考虑的 monad。
例如,PredTrans.pushArg 通常用于状态 monad,但也可用于实现读取器 monad 的实例,将读取器的值视为只读状态。
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.
Std.Do.PredTrans.pushOption.{u_1} {ps : PostShape} {α : Type u_1} (x : OptionT (PredTrans ps) α) : PredTrans (PostShape.except PUnit ps) αStd.Do.PredTrans.pushOption.{u_1} {ps : PostShape} {α : Type u_1} (x : OptionT (PredTrans ps) α) : PredTrans (PostShape.except PUnit ps) α
Adds the ability to make assertions about early termination to a predicate transformer with
postcondition shape ps, resulting in postcondition shape .except PUnit ps. This is done by
interpreting OptionT (PredTrans ps) α into PredTrans (.except PUnit ps) α, which models the type
Option as being equivalent to Except PUnit.
monad 的 weakest precondition 语义由 WP 类型类提供。
WP 的实例确定 monad 的后置条件形状,并提供将 monad 的操作解释为其后置条件形状中的谓词变换器的逻辑规则。
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.
除了 WP 实例之外,mvcgen 的大多数内置规范引理还依赖于 WPMonad 实例的存在。
除了合法之外,单子实现 pure 和 bind 的最弱前提条件还应该对应于谓词变换单子的 pure 和 bind 运算符。
如果没有 WPMonad 实例,mvcgen 通常会返回原始证明目标不变。
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.
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
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 使用其 pure 和 bind 的规范。
这往往表现为等于原始目标的验证条件。
该函数反转列表:
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 := α✝: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
当验证条件只是原始问题时,甚至没有对 bind 进行任何简化,问题通常是缺少 WPMonad 实例。
该问题可以通过添加合适的实例来解决:
instance : WPMonad Identity .pure where
wp_pure _ := rfl
wp_bind _ _ := rfl
通过这个实例,以及合适的不变量,mvcgen 和 grind 可以证明该定理。
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! 🐙
可以从纯代码调用的 Monad 通常提供一个调用运算符,该运算符将任何所需的输入状态作为参数,并返回与输出状态配对的值或某种异常值。
示例包括 StateT.run、ExceptT.run 和 Id.run。
Adequacy lemmas 在有关单子程序调用的语句与这些程序的 最弱前提条件 语义(由其 WP 实例给出)之间提供桥梁。
它们表明,如果最弱的前提条件为真,则有关调用的属性为真。
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 xStd.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 => ⌜P a⌝) s) → P xStd.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.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 xStd.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.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 xStd.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.
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 的状态。
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) : PropStd.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
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⦄ 是 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.
规范引理是将霍尔三元组与函数关联起来的指定定理。
当 mvcgen 遇到函数时,它会检查是否有任何已注册的规范引理,并尝试使用它们来释放中间 验证条件。
如果没有适用的规范引理,则语句的前置条件和后置条件之间的连接将成为验证条件。
规范引理允许对一元代码库进行组合推理。
当应用于其陈述为 Hoare 三元组的定理时,spec 属性将该定理注册为规范引理。
这些引理按优先级顺序使用。
spec 属性也可以应用于定义。
在定义上,表示验证条件生成时应展开定义。
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.
规范引理中的全称量化变量可用于将输入状态与输出状态和返回值相关联。 这些变量称为 原理图变量。
def double : StateM Nat Unit := do
modify (2 * ·)
它的规范应该与初始状态和最终状态相关,但它无法知道它们的精确值。 该规范使用一个示意性变量来代表初始状态:
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! 🐙
前提条件中的断言是一个函数,因为 StateM Nat 的 PostShape 是 .arg Nat .pure,并且 Assertion (.arg Nat .pure) 是 SPred [Nat]。
这些类型用于不变量。
ForIn.forIn 和 ForIn'.forIn' 的 规范引理 采用 Invariant 类型的参数,并且 mvcgen 确保其他自动化不会意外生成不变量。
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.
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.
不变量使用列表对 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 将列表中的位置表示为位置左侧元素和右侧元素的组合。
这种类型不是传统的拉链,其中前缀是相反的以实现高效移动:它旨在用于规范和证明,而不是运行时代码,因此前缀按原始顺序排列。
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.
Creates a cursor at the end of the list. The prefix is the entire list and the suffix is empty.