Lean 语言参考

7.6. 递归定义🔗

允许任意递归函数定义会使 Lean 的逻辑不一致。 一般递归使得编写循环证明成为可能:“命题 P 为真,因为命题 P 为真”。 在证明之外,可以将无限循环分配为类型 Empty,它可以与 Lean.Parser.Term.nomatch : termEmpty match/ex falso. `nomatch e` is of arbitrary type `α : Sort u` if Lean can show that an empty set of patterns is exhaustive given `e`'s type, e.g. because it has no constructors. nomatchEmpty.rec 一起使用来证明任何定理。

完全禁止递归函数定义将使 Lean 的用处大大降低:归纳类型 是定义谓词和数据的关键,并且它们具有递归结构。 此外,大多数有用的递归函数不会威胁到健全性,并且无限循环通常表明定义中的错误而不是有意的行为。 Lean 要求安全地定义每个递归函数,而不是禁止递归函数。 在详细精化递归定义时,Lean精化器还提供了所定义的函数是安全的理由。精化概述中有关 精化器的输出的部分将上下文背景化精化的精化器整体上下文中的递归定义。

可以定义六种主要的递归函数:

结构递归函数

结构递归函数采用一个参数,以便该函数仅对所述参数的严格子组件进行递归调用。严格来说,类型为 索引族 的参数与其索引分组在一起,整个集合被视为一个单元。 精化器将递归转换为参数的 recursor 的使用。 因为递归器的每次类型正确使用都保证避免无限回归,所以这个翻译是函数终止的证据。 通过递归器定义的函数的应用程序在定义上等于递归的结果,并且通常在内核内相对有效。

对有根据的关系进行递归

很多函数也很难转换为结构递归;例如,函数可能会终止,因为数组索引和数组大小之间的差异随着索引的增加而减小,但 Nat.rec 不适用,因为增加的索引是函数的参数。 此处,终止的 measure 在每次递归调用时都会减少,但该度量本身并不是函数的参数。 在这些情况下,良基递归 可用于定义函数。 良基递归是一种将具有递减测度的递归函数系统地转换为递归函数的技术,并证明每个测度递减序列最终都会以最小值终止。 通过良基递归定义的函数的应用程序不一定在定义上等于它们的返回值,但这种等式可以作为命题来证明。 即使存在定义等式,这些函数的计算速度通常也很慢,因为它们需要减少通常非常大的证明项。

作为部分不动点的递归函数

函数的定义可以理解为指定其行为的方程。 在某些情况下,即使递归函数不一定对所有输入都终止,也可以证明满足此规范的函数的存在。 该策略甚至适用于函数定义不一定针对所有输入终止的某些情况。 这些偏函数作为这些方程的固定点出现,称为 部分固定点

特别是,任何返回类型位于某些 monad 中的函数(例如 Option)都可以使用此策略来定义。 Lean 为这些一元函数生成附加的部分正确性定理。 与良基递归一样,定义为部分不动点的函数的应用在定义上并不等于其返回值,但 Lean 生成的定理在命题上将函数等同于其展开以及其定义中指定的归约行为。

作为固定点的共归纳和归纳谓词

递归 Prop 值函数可以定义为完整格上单调算子的最大或最小不动点。 使用 Lean.Parser.Command.declaration : commandcoinductive_fixpointLean.Parser.Command.declaration : commandcoinductive 命令定义的共归纳谓词描述潜在的无限行为,例如无限序列或互模拟。 使用 Lean.Parser.Command.declaration : commandinductive_fixpoint 定义的归纳谓词提供了标准归纳类型的替代方案,该替代方案与混合归纳-共归纳互块兼容。

具有非空余域的偏函数

对于许多应用程序来说,推理某些功能的实现并不重要。 递归函数可能仅用作证明自动化步骤实现的一部分,或者它可能是一个永远不会被正式证明正确的普通程序。 在这些情况下,Lean内核不需要定义或命题等式来维持定义;保持健全性就足够了。 标记为 Lean.Parser.Command.declaration : commandpartial 的函数被内核视为不透明常量,既不展开也不还原。 健全性所需要的只是它们的返回类型是可居住的。 偏函数仍然可以像往常一样在编译代码中使用,并且它们可以出现在命题和证明中;他们的方程理论在Lean的逻辑中简直是非常薄弱。

不安全的递归定义

不安全定义没有部分定义的任何限制。 他们可以自由地使用一般递归,并且可以使用 Lean 的功能来打破有关其方程理论的假设,例如用于转换的原语 (unsafeCast)、检查指针相等性 (ptrAddrUnsafe) 以及观察 引用计数 (isExclusiveUnsafe)。 但是,任何引用不安全定义的声明本身都必须标记为 Lean.Parser.Command.declaration : commandunsafe,以明确何时无法保证逻辑健全性。 不安全操作可用于将其他函数的实现替换为编译代码中更有效的变体,而内核仍使用原始定义。 被替换的函数可能是不透明的,这导致函数名称在逻辑中具有琐碎的方程理论,或者它可能是普通函数,在这种情况下该函数在逻辑中使用。 请谨慎使用此功能:逻辑健全性不会受到威胁,但如果不安全的实现不正确,则用 Lean 编写的程序的行为可能会偏离其经过验证的逻辑模型。

精化器输出概述中所述,递归函数的精化分两个阶段进行:

  1. 该定义的详细说明就像 Lean 的核心 类型论 具有递归定义一样。 除了使用递归之外,这个临时定义还得到了充分的精化。 编译器根据这些临时定义生成代码。

  2. 终止分析尝试使用五种技术来验证 Lean 的内核的功能。 如果定义标记为 Lean.Parser.Command.declaration : commandunsafeLean.Parser.Command.declaration : commandpartial,则使用该技术。 如果存在显式 Lean.Parser.Command.declaration : commandtermination_byLean.Parser.Command.declaration : commandpartial_fixpointLean.Parser.Command.declaration : commandcoinductive_fixpointLean.Parser.Command.declaration : commandinductive_fixpoint 子句,则所指示的技术是唯一尝试的一种技术。 如果不存在此类子句,则精化器执行搜索,测试函数的每个参数作为结构递归的候选者,并尝试查找具有在每次递归调用时减少的有充分依据的关系的度量。

本节描述管理递归函数的规则。 在描述了相互递归之后,指定了五种递归定义中的每一种,以及每种递归定义的推理能力和灵活性之间的权衡。

7.6.1. 相互递归🔗

正如递归定义是在定义主体中提及所定义的名称一样,mutually recursive 定义是可以递归或互相提及的定义。 要在多个声明之间使用相互递归,必须将它们放置在 相互块 中。

syntaxMutual Declaration Blocks

相互递归的一般语法是:

command ::= ...
    | mutual
        declaration*
      end

其中声明必须是定义或定理。

相互块中的声明不在彼此签名的范围内,但在彼此主体的范围内。 即使名称不在签名范围内,它们也不会作为自动绑定隐式参数插入。

Mutual Block Scope

相互块中定义的名称不在彼此签名的范围内。

mutual abbrev NaturalNum : Type := Nat def n : Unknown identifier `NaturalNum`NaturalNum := 5 end
Unknown identifier `NaturalNum`

没有相互块,定义成功:

abbrev NaturalNum : Type := Nat def n : NaturalNum := 5
Mutual Block Scope and Automatic Implicit Parameters

相互块中定义的名称不在彼此签名的范围内。 尽管如此,它们不能用作自动隐式参数:

mutual abbrev α : Type := Nat def identity (x : Unknown identifier `α`α) : Unknown identifier `α`α := x end
Unknown identifier `α`

使用不同的名称,会自动添加隐式参数:

mutual abbrev α : Type := Nat def identity (x : β) : β := x end

详细说明递归定义总是以交互块的粒度进行,就好像每个声明周围都有一个单例交互块,而该声明本身不是该块的一部分。 通过 Lean.Parser.Term.letrec : termlet rec 引入的本地定义和 Lean.Parser.Command.declaration : commandwhere 脱离其上下文,根据需要引入捕获的自由变量的参数,并将它们视为 Lean.Parser.Command.mutual : commandmutual 块内的单独定义。 因此,Lean.Parser.Command.declaration : commandwhere 块中定义的帮助器可以相互使用相互递归,也可以与它们所在的定义使用相互递归,但它们可能不会在类型签名中相互提及。

在精化的第一步之后(其中定义仍然是递归的),并且在使用上述技术转换递归之前,Lean 在相互块中的定义中识别实际(相互)递归派 ,并按依赖顺序单独处理它们。

7.6.2. 结构递归🔗

结构递归函数是指每次递归调用的结构项都小于参数的项。 相同的参数必须在所有递归调用中减少;该参数称为 递减参数。 结构递归比递归器提供的原始递归更强,因为递归调用可以使用参数的更深层嵌套的子项,而不仅仅是直接子项。 然而,用于实现结构递归的结构是使用递归器实现的;这些辅助结构在 关于归纳类型的部分中进行了描述。

管理结构递归的规则本质上是语法的。 有许多递归定义表现出结构递归计算行为,但不被这些规则所接受;这是全自动分析的基本结果。 良基递归 提供了一种语义方法来演示终止,该方法可用于递归函数不是结构递归的情况,但也可以在根据结构递归计算的函数不满足语法要求时使用。

Structural Recursion vs Subtraction

函数 countdown 在结构上是递归的。 The parameter n was matched against the pattern n' + 1, which means that n' is a direct subterm of n in the second branch of the pattern match:

def countdown (n : Nat) : List Nat := match n with | 0 => [] | n' + 1 => n' :: countdown n'

将模式匹配替换为等效的布尔测试和减法会导致错误:

def fail to show termination for countdown' with errors failed to infer structural recursion: Cannot use parameter n: failed to eliminate recursive application countdown' n' failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goal n:Nath✝:¬(n == 0) = truen':Nat := n - 1n - 1 < ncountdown' (n : Nat) : List Nat := if n == 0 then [] else let n' := n - 1 n' :: countdown' n'
fail to show termination for
  countdown'
with errors
failed to infer structural recursion:
Cannot use parameter n:
  failed to eliminate recursive application
    countdown' n'


failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
n:Nath✝:¬(n == 0) = truen':Nat := n - 1n - 1 < n

这是因为参数 n 上没有模式匹配。 虽然此函数确实终止,但它这样做的论点是基于 if、相等测试和减法的属性,而不是 Nat归纳类型 的通用特征。 这些参数使用 良基递归 表示,对函数定义的轻微更改允许 Lean 自动支持良基递归来构造替代终止证明。 此版本基于 Nat命题等价 的可判定性进行分支,而不是布尔相等测试的结果:

def countdown' (n : Nat) : List Nat := if n = 0 then [] else let n' := n - 1 n' :: countdown' n'

在这里,Lean 的自动化自动根据有关 命题等价 和减法的事实构建终止证明。 它在幕后使用良基递归而不是结构递归。

结构递归可以显式或自动使用。 对于显式结构递归,函数定义声明哪个参数是 递减参数。 如果未显式声明终止策略,Lean 将搜索递减参数以及与 良基递归 一起使用的递减度量。 显式注释结构递归有以下好处:

  • 它可以加速精化,因为没有搜索发生。

  • 它为读者记录了终止论证。

  • 在明确需要结构递归的情况下,它可以防止意外使用良基递归。

7.6.2.1. 显式结构递归🔗

要显式使用结构递归,可以使用指定 递减参数Lean.Parser.Command.declaration : commandtermination_by structural 子句来注释函数或定理定义。 递减的参数可以是对签名中命名的参数的引用。 当签名指定函数类型时,递减的参数还可以是签名中未命名的参数;在这种情况下,可以通过将其余参数的名称写在箭头之前来引入它们(Lean.Parser.Command.declaration : command=>)。

Specifying Decreasing Parameters

当递减参数是函数的命名参数时,可以通过引用其名称来指定。

def half (n : Nat) : Nat := match n with | 0 | 1 => 0 | n + 2 => half n + 1 termination_by structural n

当签名中未命名递减参数时,可以在 Lean.Parser.Command.declaration : commandtermination_by 子句中本地引入名称。

def half : Nat Nat | 0 | 1 => 0 | n + 2 => half n + 1 termination_by structural n => n
syntaxExplicit Structural Recursion

termination_by structural 子句引入了递减参数。

Specify a termination measure for recursive functions.
```
termination_by a - b
```
indicates that termination of the currently defined recursive function follows
because the difference between the arguments `a` and `b` decreases.

If the function takes further argument after the colon, you can name them as follows:
```
def example (a : Nat) : Nat → Nat → Nat :=
termination_by b c => a - b
```

By default, a `termination_by` clause will cause the function to be constructed using well-founded
recursion. The syntax `termination_by structural a` (or `termination_by structural _ c => c`)
indicates the function is expected to be structural recursive on the argument. In this case
the body of the `termination_by` clause must be one of the function's parameters.

If omitted, a termination measure will be inferred. If written as `termination_by?`,
the inferred termination measure will be suggested.

terminationBy ::= ...
    | Specify a termination measure for recursive functions.
```
termination_by a - b
```
indicates that termination of the currently defined recursive function follows
because the difference between the arguments `a` and `b` decreases.

If the function takes further argument after the colon, you can name them as follows:
```
def example (a : Nat) : Nat → Nat → Nat :=
termination_by b c => a - b
```

By default, a `termination_by` clause will cause the function to be constructed using well-founded
recursion. The syntax `termination_by structural a` (or `termination_by structural _ c => c`)
indicates the function is expected to be structural recursive on the argument. In this case
the body of the `termination_by` clause must be one of the function's parameters.

If omitted, a termination measure will be inferred. If written as `termination_by?`,
the inferred termination measure will be suggested.

termination_by structural (ident* =>)? term

可选 => 之前的标识符可以将函数参数带入非 已经绑定在声明头中,并且强制术语必须指示函数的参数之一,无论是在头中引入还是在子句中本地引入。

递减参数必须满足以下条件:

  • 其类型必须是 归纳类型

  • 如果其类型是 indexed family,则所有索引都必须是函数的参数。

  • 如果递减参数的归纳或索引族具有数据类型参数,则这些数据类型参数本身可能仅依赖于属于 固定前缀 的函数参数。

fixedparameter 是在所有递归调用中未经修改地传递的函数参数,并且不是递归参数类型的索引。 fixed prefix 是函数参数的最长前缀,其中所有参数都是固定的。

Ineligible decreasing parameters

递减参数的类型必须是归纳类型。 在notInductive中,指定了一个函数作为递减参数:

def notInductive (x : Nat Nat) : Nat := notInductive (fun n => x (n+1)) cannot use specified measure for structural recursion: its type is not an inductivetermination_by structural x
cannot use specified measure for structural recursion:
  its type is not an inductive

如果递减参数是索引族,则所有索引都必须是变量。 在 constantIndex 中,索引系列 Fin' 改为应用于常量值:

inductive Fin' : Nat Type where | zero : Fin' (n+1) | succ : Fin' n Fin' (n+1) def constantIndex (x : Fin' 100) : Nat := constantIndex .zero cannot use specified measure for structural recursion: its type Fin' is an inductive family and indices are not variables Fin' 100termination_by structural x
cannot use specified measure for structural recursion:
  its type Fin' is an inductive family and indices are not variables
    Fin' 100

递减参数类型的参数不得依赖于变化参数或索引之后的函数参数。 在afterVarying中,固定前缀为空,因为第一个参数n变化,所以p不是固定前缀的一部分:

inductive WithParam' (p : Nat) : Nat Type where | zero : WithParam' p (n+1) | succ : WithParam' p n WithParam' p (n+1) failed to infer structural recursion: Cannot use parameter x: failed to eliminate recursive application afterVarying (n + 1) p WithParam'.zero def afterVarying (n : Nat) (p : Nat) (x : WithParam' p n) : Nat := afterVarying (n+1) p .zero termination_by structural x
failed to infer structural recursion:
Cannot use parameter x:
  failed to eliminate recursive application
    afterVarying (n + 1) p WithParam'.zero

此外,函数的每次递归调用都必须在递减的 strict 子项上 参数。

  • 递减参数本身是一个子项,但不是严格的子项。

  • 如果子项是 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 表达式或其他模式匹配语法的 判别式,则与判别式匹配的模式是每个 匹配替代项右侧 中的子项。 特别是,匹配泛化的规则用于将判别式连接到右侧模式项的出现;因此,它尊重 定义等价。 当且仅当判别式是严格子项时,该模式才是严格子项。

  • 如果子项是应用于参数的构造函数,则其递归参数是严格子项。

Nested Patterns and Sub-Terms

在以下示例中,递减参数 n 与嵌套模式 .succ (.succ n) 进行匹配。因此,.succ (.succ n)n 的(非严格)子术语,因此 n.succ n 都是严格子术语,并且定义被接受。

def fib : Nat Nat | 0 | 1 => 1 | .succ (.succ n) => fib n + fib (.succ n) termination_by structural n => n

为清楚起见,本示例使用 .succ n.succ (.succ n),而不是等效的 Nat 特定的 n+1n+2

Matching on Complex Expressions Can Prevent Elaboration

在以下示例中,递减参数 n 并不直接是 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 表达式的 判别式。 因此,n' 不被视为 n 的子术语。

failed to infer structural recursion: Cannot use parameter n: failed to eliminate recursive application half n' def half (n : Nat) : Nat := match Option.some n with | .some (n' + 2) => half n' + 1 | _ => 0 termination_by structural n
failed to infer structural recursion:
Cannot use parameter n:
  failed to eliminate recursive application
    half n'

使用 良基递归,并将判别式显式连接到匹配模式,可以接受此定义。

def half (n : Nat) : Nat := match h : Option.some n with | .some (n' + 2) => half n' + 1 | _ => 0 termination_by n decreasing_by n:Natn':Nath:n = n' + 1 + 1n' < n' + 1 + 1; All goals completed! 🐙

同样,以下示例失败:尽管 xs.tail 会简化为 xs 的严格子项,但根据上述规则,这对 Lean 不可见。 特别是,xs.tail定义等于 xs 的严格子项。

failed to infer structural recursion: Cannot use parameter #2: failed to eliminate recursive application listLen xs.tail def listLen : List α Nat | [] => 0 | xs => listLen xs.tail + 1 termination_by structural xs => xs
Simultaneous Matching vs Matching Pairs for Structural Recursion

用于证明终止的策略的一个重要结果是两个 判别式的同时匹配并不等于匹配一对。 同时匹配保持判别式和模式之间的联系,允许模式匹配细化本地上下文中的假设类型以及 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 的预期类型。 本质上,Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 的精化规则会特殊对待判别式,并且以保留程序运行时含义的方式更改判别式不一定会保留编译时含义。

此函数查找两个自然数中的最小值,由结构递归在其第一个参数上定义:

def min' (n k : Nat) : Nat := match n, k with | 0, _ => 0 | _, 0 => 0 | n' + 1, k' + 1 => min' n' k' + 1 termination_by structural n

将两个参数上的同时模式匹配替换为一对上的匹配会导致终止分析失败:

failed to infer structural recursion: Cannot use parameter n: failed to eliminate recursive application min' n' k' def min' (n k : Nat) : Nat := match (n, k) with | (0, _) => 0 | (_, 0) => 0 | (n' + 1, k' + 1) => min' n' k' + 1 termination_by structural n
failed to infer structural recursion:
Cannot use parameter n:
  failed to eliminate recursive application
    min' n' k'

这是因为当将递归调用与严格较小的参数值匹配时,分析仅考虑参数上的直接模式匹配。 将判别式包装成一对会破坏连接。

Structural Recursion Under Pairs

无法通过结构递归详细说明此函数,该函数用于查找一对的两个分量中的最小值。

failed to infer structural recursion: Cannot use parameter nk: the type Nat × Nat does not have a `.brecOn` recursor def min' (nk : Nat × Nat) : Nat := match nk with | (0, _) => 0 | (_, 0) => 0 | (n' + 1, k' + 1) => min' (n', k') + 1 termination_by structural nk
failed to infer structural recursion:
Cannot use parameter nk:
  the type Nat × Nat does not have a `.brecOn` recursor

这是因为参数的类型 Prod 不是递归的。 因此,其构造函数没有可由模式匹配公开的递归参数。

使用 良基递归 可以接受此定义,但是:

def min' (nk : Nat × Nat) : Nat := match nk with | (0, _) => 0 | (_, 0) => 0 | (n' + 1, k' + 1) => min' (n', k') + 1 termination_by nk
Structural Recursion and Definitional Equality

即使 countdown 的递归出现应用于不是递减参数的严格子项的项,也接受以下定义:

def countdown (n : Nat) : List Nat := match n with | 0 => [] | n' + 1 => n' :: countdown (n' + 0) termination_by structural n

这是因为 n' + 0定义等于 n',这是 n 的严格子术语。 从模式匹配生成的 子项 使用 匹配泛化 的规则连接到 判别,该规则尊重 定义等价。

countdown' 中,递归出现应用于 0 + n',它在定义上并不等于 n',因为自然数上的加法在其第二个参数中是结构递归的:

failed to infer structural recursion: Cannot use parameter n: failed to eliminate recursive application countdown' (0 + n') def countdown' (n : Nat) : List Nat := match n with | 0 => [] | n' + 1 => n' :: countdown' (0 + n') termination_by structural n
failed to infer structural recursion:
Cannot use parameter n:
  failed to eliminate recursive application
    countdown' (0 + n')

7.6.2.2. 相互结构递归🔗

Lean 支持使用结构递归定义 相互递归函数。 可以使用 mutual block 引入相互递归,但它也可以由 Lean.Parser.Term.letrec : termlet rec 表达式和 Lean.Parser.Command.declaration : commandwhere 块产生。 相互结构递归的规则应用于一组实际相互递归、提升的定义,这些定义由相互组的 精化步骤 产生。 如果共同组中的每个函数都有一个 termination_by structural 注释来指示该函数的递减参数,则使用结构递归来转换定义。

对上述递减参数的要求进行扩展:

  • 所有递减参数的所有类型必须来自同一归纳类型,或者更一般地来自同一 归纳类型的共同组

  • 对于所有函数,递减参数类型的参数必须相同,并且可能仅取决于函数参数的 common 固定前缀。

这些功能不必与相互的归纳类型一对一对应。 多个函数可以具有相同类型的递减参数,并且并非所有与递减参数相互递归的类型都需要具有相应的函数。

Mutual Structural Recursion Over Non-Mutual Types

以下示例演示了非互归纳数据类型上的相互递归:

mutual def even : Nat Prop | 0 => True | n+1 => odd n termination_by structural n => n def odd : Nat Prop | 0 => False | n+1 => even n termination_by structural n => n end
Mutual Structural Recursion Over Mutual Types

以下示例演示了相互归纳类型上的递归。 函数 Exp.sizeApp.size 是相互递归的。

mutual inductive Exp where | var : String Exp | app : App Exp inductive App where | fn : String App | app : App Exp App end mutual def Exp.size : Exp Nat | .var _ => 1 | .app a => a.size termination_by structural e => e def App.size : App Nat | .fn _ => 1 | .app a e => a.size + e.size + 1 termination_by structural a => a end

App.numArgs 的定义在类型 App 上进行结构递归。 说明并非交互组中的所有归纳类型都需要处理。

def App.numArgs : App Nat | .fn _ => 0 | .app a _ => a.numArgs + 1 termination_by structural a => a

7.6.2.3. 推断结构递归🔗

如果递归或互递归函数定义中不存在 termination_by 子句,则 Lean 尝试通过按顺序尝试所有合适的参数来有效地推断合适的结构递减参数。 如果此搜索失败,Lean 将尝试推断 良基递归

对于相互递归函数,会尝试所有参数组合,直至达到极限以避免组合爆炸。 如果只有部分相互递归函数具有 termination_by structural 子句,则仅考虑这些参数,而对于其他函数,则考虑结构递归的所有参数。

termination_by? 子句导致显示推断的终止注释。 可以使用提供的建议或代码操作将其自动添加到源文件中。

Inferred Termination Annotations

Lean 自动推断函数 half 在结构上是递归的。 termination_by? 子句会导致显示推断的终止注释,并且只需单击一下即可将其自动添加到源文件中。

def half : Nat Nat | 0 | 1 => 0 | n + 2 => half n + 1 Try this: [apply] termination_by structural x => xtermination_by?
Try this:
  [apply] termination_by structural x => x

7.6.2.4. 精化使用值过程递归🔗

在本节中,将更详细地解释用于精化结构递归函数的构造。 此精化使用从归纳类型递归器自动生成的 belowbrecOn 结构

Recursion vs Recursors

自然数的添加可以通过第二个参数的递归来定义。 该函数在结构上是直接递归的。

def add (n : Nat) : Nat Nat | .zero => n | .succ k => .succ (add n k)

使用 Nat.rec 定义,它与大多数人习惯的符号相距甚远。

def add' (n : Nat) := Nat.rec (motive := fun _ => Nat) n (fun Variable name `k` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`k soFar => .succ soFar)

对不是函数参数的直接子代的数据进行的结构递归调用需要创造力或复杂但系统的编码。

def half : Nat Nat | 0 | 1 => 0 | n + 2 => half n + 1

将此函数视为结构递归,它在每次调用时翻转一位,仅在设置该位时递增结果。

def helper : Nat Bool Nat := Nat.rec (motive := fun _ => Bool Nat) (fun _ => 0) (fun _ soFar => fun b => (if b then Nat.succ else id) (soFar !b)) def half' (n : Nat) : Nat := helper n false [0, 0, 1, 1, 2, 2, 3, 3, 4]#eval [0, 1, 2, 3, 4, 5, 6, 7, 8].map half'
[0, 0, 1, 1, 2, 2, 3, 3, 4]

可以使用称为 值过程递归 的通用技术来代替创造力。 值过程递归使用可以为每个归纳类型系统导出的帮助程序,根据递归器定义; Lean 自动导出它们。 对于每个 Nat n,类型 n.below (motive := mot) 为所有 k < n 提供 mot k 类型的值,表示为迭代的 相关对类型。 值过程递归器 Nat.brecOn 允许函数使用任何较小的 Nat 的结果。 用它来定义函数很不方便:

noncomputable def half'' (n : Nat) : Nat := Nat.brecOn n (motive := fun _ => Nat) fun k soFar => match k, soFar with | 0, _ | 1, _ => 0 | _ + 2, _, h, _ => h + 1

该函数被标记为 Lean.Parser.Command.declaration : commandnoncomputable,因为编译器不支持生成值过程递归的代码,该递归旨在用于推理而不是高效的代码。 内核仍可用于测试该功能,但是:

[0, 0, 1, 1, 2, 2, 3, 3, 4]#reduce [0,1,2,3,4,5,6,7,8].map half''
[0, 0, 1, 1, 2, 2, 3, 3, 4]

如果需要,half'' 主体中的依赖模式匹配也可以使用递归器(具体来说,Nat.casesOn)进行编码:

noncomputable def half''' (n : Nat) : Nat := n.brecOn (motive := fun _ => Nat) fun k => k.casesOn (motive := fun k' => (k'.below (motive := fun _ => Nat)) Nat) (fun _ => 0) (fun k' => k'.casesOn (motive := fun k'' => (k''.succ.below (motive := fun _ => Nat)) Nat) (fun _ => 0) (fun _ soFar => soFar.2.1.succ))

这个定义仍然有效。

[0, 0, 1, 1, 2, 2, 3, 3, 4]#reduce [0,1,2,3,4,5,6,7,8].map half''
[0, 0, 1, 1, 2, 2, 3, 3, 4]

然而,现在它与最初的定义相去甚远,对于大多数人来说已经变得难以理解。 递归是一个很好的逻辑基础,但不是编写程序或证明的简单方法。

结构递归分析尝试将递归 预定义 转换为适当的结构递归结构的使用。 到这一步,模式匹配已经被翻译成匹配器函数的使用;这些由终止检查器进行特殊处理。 接下来,对于每组参数,尝试使用 brecOn 进行转换。

Course-of-Values Tables

该定义等价于 List.below

def List.below' {α : Type u} {motive : List α Sort u} : List α Sort (max (u + 1) u) | [] => PUnit | _ :: xs => motive xs ×' xs.below' (motive := motive)

换句话说,对于给定的 motiveList.below' 是包含列表中所有后缀的动机实现的类型。

更多的递归参数需要产品类型的进一步嵌套迭代。 例如,二叉树有两次递归出现。

inductive Tree (α : Type u) : Type u where | leaf | branch (left : Tree α) (val : α) (right : Tree α)

其对应的值过程表包含所有子树动机的实现:

def Tree.below' {α : Type u} {motive : Tree α Sort u} : Tree α Sort (max (u + 1) u) | .leaf => PUnit | .branch left _val right => (motive left ×' left.below' (motive := motive)) ×' (motive right ×' right.below' (motive := motive))

对于列表和树,brecOn 运算符只需要一种情况,而不是每个构造函数一种情况。 这种情况接受一个列表或树以及所有较小值的结果表;由此看来,它应该满足提供价值的动机。 对所提供值的相关案例分析会自动细化备注表的类型,提供所需的一切。

以下定义分别相当于 List.brecOnTree.brecOn。 原始递归助手 List.brecOnTableTree.brecOnTable 计算值过程表以及最终结果,而 brecOn 运算符的实际定义只是投影出结果。

def List.brecOnTable {α : Type u} {motive : List α Sort u} (xs : List α) (step : (ys : List α) ys.below' (motive := motive) motive ys) : motive xs ×' xs.below' (motive := motive) := match xs with | [] => step [] PUnit.unit, PUnit.unit | x :: xs => let res := xs.brecOnTable (motive := motive) step let val := step (x :: xs) res val, res def Tree.brecOnTable {α : Type u} {motive : Tree α Sort u} (t : Tree α) (step : (ys : Tree α) ys.below' (motive := motive) motive ys) : motive t ×' t.below' (motive := motive) := match t with | .leaf => step .leaf PUnit.unit, PUnit.unit | .branch left val right => let resLeft := left.brecOnTable (motive := motive) step let resRight := right.brecOnTable (motive := motive) step let branchRes := resLeft, resRight let val := step (.branch left val right) branchRes val, branchRes def List.brecOn' {α : Type u} {motive : List α Sort u} (xs : List α) (step : (ys : List α) ys.below' (motive := motive) motive ys) : motive xs := (xs.brecOnTable (motive := motive) step).1 def Tree.brecOn' {α : Type u} {motive : Tree α Sort u} (t : Tree α) (step : (ys : Tree α) ys.below' (motive := motive) motive ys) : motive t := (t.brecOnTable (motive := motive) step).1

below 构造是从类型的每个值到对所有较小值进行某些函数调用的结果的映射;它可以理解为一个记忆表,其中已经包含了所有较小值的结果。 below 结构中表达的“较小值”的概念直接对应于 严格子术语的定义。

递归器需要归纳类型的每个构造函数都有一个参数;在 ι-reduction 期间,使用构造函数的参数(以及递归参数的递归结果)调用这些参数。 另一方面,值过程递归运算符 brecOn 只需要一个一次性覆盖所有构造函数的情况。 这种情况提供了一个值和一个 below 表,该表包含所有小于给定值的值的递归结果;它应该使用表的内容来满足提供值的动机。 如果函数在给定参数(或参数组)上进行结构递归,则所有递归调用的结果都将出现在该表中。

当递归函数的主体转换为对函数参数之一的 brecOn 的调用时,该参数及其值过程表位于范围内。 分析遍历函数体,寻找递归调用。 如果参数匹配,则其在本地上下文中的出现次数为 generalized,然后使用模式实例化;对于值过程表的类型也是如此。 通常,此模式匹配会导致值过程表的类型变得更加具体,从而可以访问较小值的递归结果。 该泛化过程实现了模式是匹配判别式的 子项 的规则。 当检测到函数的递归出现时,将查阅值过程表以查看它是否包含正在检查的参数的结果。 如果是这样,则可以用表中的投影来替换递归调用。 如果不是,则相关参数不支持结构递归。

Elaboration Walkthrough

遍历 half 的精化的第一步是将其手动脱糖为更简单的形式。 这与 Lean 的工作方式不匹配,但当存在的 OfNat 实例较少时,其输出更容易阅读。 这个可读的定义:

def half : Nat Nat | 0 | 1 => 0 | n + 2 => half n + 1

可以重写为这个较低级别的版本:

def half : Nat Nat | .zero | .succ .zero => .zero | .succ (.succ n) => half n |>.succ

精化器首先精化了一个预定义,其中递归仍然存在,但定义在 Lean 的核心 类型论 中。 打开编译器对预定义的跟踪,并使漂亮的打印机更加明确,使生成的预定义可见:

set_option trace.Elab.definition.body true in set_option pp.all true in [Elab.definition.body] half : Nat Nat := fun (x : Nat) => half.match_1.{1} (fun (x : Nat) => Nat) x (fun (_ : Unit) => Nat.zero) (fun (_ : Unit) => Nat.zero) fun (n : Nat) => Nat.succ (half n)def half : Nat Nat | .zero | .succ .zero => .zero | .succ (.succ n) => half n |>.succ

返回的跟踪消息是:

[Elab.definition.body] half : Nat → Nat :=
    fun (x : Nat) =>
      half.match_1.{1} (fun (x : Nat) => Nat) x
        (fun (_ : Unit) => Nat.zero)
        (fun (_ : Unit) => Nat.zero)
        fun (n : Nat) => Nat.succ (half n)

辅助匹配函数的定义为:

@[implicit_reducible] def half.match_1.{u_1} : (motive : Nat Sort u_1) (x : Nat) (Unit motive Nat.zero) (Unit motive 1) ((n : Nat) motive n.succ.succ) motive x := fun motive x h_1 h_2 h_3 => Nat.casesOn x (h_1 ()) fun n => Nat.casesOn n (h_2 ()) fun n => h_3 n#print half.match_1
@[implicit_reducible] def half.match_1.{u_1} : (motive : Nat  Sort u_1) 
  (x : Nat)  (Unit  motive Nat.zero)  (Unit  motive 1)  ((n : Nat)  motive n.succ.succ)  motive x :=
fun motive x h_1 h_2 h_3 => Nat.casesOn x (h_1 ()) fun n => Nat.casesOn n (h_2 ()) fun n => h_3 n

格式更易读,这个定义是:

def half.match_1'.{u} : (motive : Nat Sort u) (x : Nat) (Unit motive Nat.zero) (Unit motive 1) ((n : Nat) motive n.succ.succ) motive x := fun Variable name `motive` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`motive x h_1 h_2 h_3 => Nat.casesOn x (h_1 ()) fun n => Nat.casesOn n (h_2 ()) fun n => h_3 n

换句话说,half 中使用的模式的具体配置在 half.match_1 中捕获。

该定义是 half 预定义的更具可读性的版本:

def half' : Nat Nat := fun (x : Nat) => half.match_1 (motive := fun _ => Nat) x (fun _ => 0) -- Case for 0 (fun _ => 0) -- Case for 1 (fun n => Nat.succ (half' n)) -- Case for n + 2

要将其精化为结构递归函数,第一步是建立 bRecOn 调用。 该定义必须标记为 Lean.Parser.Command.declaration : commandnoncomputable,因为 Lean 不支持递归器(例如 Nat.brecOn)的代码生成。

noncomputable def half'' : Nat Nat := fun (x : Nat) => x.brecOn fun n table => don't know how to synthesize placeholder context: x n:Nattable:Nat.below nNat_ /- To translate: half.match_1 (motive := fun _ => Nat) x (fun _ => 0) -- Case for 0 (fun _ => 0) -- Case for 1 (fun n => Nat.succ (half' n)) -- Case for n + 2 -/

下一步是将原始函数体中出现的 x 替换为 brecOn 提供的 n。 由于 table 的类型取决于 x,因此在使用 half.match_1 拆分案例时也必须对其进行泛化,从而导致带有额外参数的动机。

noncomputable def half'' : Nat Nat := fun (x : Nat) => x.brecOn fun n table => (half.match_1 (motive := fun k => k.below (motive := fun _ => Nat) Nat) n don't know how to synthesize placeholder for argument `h_1` context: x n:Nattable:Nat.below nUnit Nat.below Nat.zero Nat_ don't know how to synthesize placeholder for argument `h_2` context: x n:Nattable:Nat.below nUnit Nat.below 1 Nat_ don't know how to synthesize placeholder for argument `h_3` context: x n:Nattable:Nat.below n(n : Nat) Nat.below n.succ.succ Nat_) table /- To translate: (fun _ => 0) -- Case for 0 (fun _ => 0) -- Case for 1 (fun n => Nat.succ (half' n)) -- Case for n + 2 -/

这三种情况的占位符需要以下类型:

don't know how to synthesize placeholder for argument `h_1`
context:
x n:Nattable:Nat.below nUnit  Nat.below Nat.zero  Nat
don't know how to synthesize placeholder for argument `h_2`
context:
x n:Nattable:Nat.below nUnit  Nat.below 1  Nat
don't know how to synthesize placeholder for argument `h_3`
context:
x n:Nattable:Nat.below n(n : Nat)  Nat.below n.succ.succ  Nat

预定义中的前两种情况是常量函数,无需检查递归:

noncomputable def half'' : Nat Nat := fun (x : Nat) => x.brecOn fun n table => (half.match_1 (motive := fun k => k.below (motive := fun _ => Nat) Nat) n (fun () _ => .zero) (fun () _ => .zero) don't know how to synthesize placeholder for argument `h_3` context: x n:Nattable:Nat.below n(n : Nat) Nat.below n.succ.succ Nat_) table /- To translate: (fun n => Nat.succ (half' n)) -- Case for n + 2 -/

最后一种情况包含递归调用。 它应该转换为对值过程表的查找。 最后一个孔类型的更易读的表示是:

(n : Nat) Nat.below (motive := fun _ => Nat) n.succ.succ Nat

这相当于

(n : Nat) Nat ×' (Nat ×' Nat.below (motive := fun _ => Nat) n) Nat

值过程表中的第一个 Natn + 1 的递归结果,第二个是 n 的递归结果。 因此,递归调用可以替换为查找,并且精化成功:

noncomputable def half'' : Nat Nat := fun (x : Nat) => x.brecOn fun n table => (half.match_1 (motive := fun k => k.below (motive := fun _ => Nat) Nat) n (fun () _ => .zero) (fun () _ => .zero) (fun _ table => Nat.succ table.2.1) table unexpected end of input; expected ')', ',' or ':'

实际的精化器通过将具有新名称的哨兵类型插入到动机中来跟踪为结构递归检查的参数与值过程表中的位置之间的关系。

7.6.3. 良基递归🔗

well-founded recursion 定义的函数是这样的函数,其中每个递归调用的参数都比函数的参数(在 适当的意义上)。 与 结构递归 不同,其中递归定义必须满足特定的语法要求,而使用良基递归的定义则采用语义参数。 这允许接受更大类别的递归定义。 此外,当Lean的自动化无法构建终止证明时,可以手动指定一个。

Lean 编译器对所有定义的处理方式相同。 在 Lean 的逻辑中,使用良基递归的定义通常不会减少 定义。 然而,这些约简确实作为命题等式成立,并且 Lean 自动证明了它们。 这通常不会使证明使用良基递归的定义的属性变得更加困难,因为命题约简可用于推理函数的行为。 然而,这确实意味着在类型中使用这些函数通常效果不佳。 即使约简行为恰好在定义上成立,它通常也比内核中的结构递归定义慢得多,后者必须随定义一起展开终止证明。 如果可能,应使用结构递归定义用于 定义等价 很重要的类型或其他情况的递归函数。

要显式使用良基递归,可以使用 Lean.Parser.Command.declaration : commandtermination_by 子句来注释函数或定理定义,该子句指定函数终止的 measure。 该度量应该是在每次递归调用时减少的项;它可以是函数的参数之一或参数的元组,但也可以是任何其他术语。 度量的类型必须配备 有充分基础的关系,它确定度量减少意味着什么。

syntaxExplicit Well-Founded Recursion

Lean.Parser.Command.declaration : commandtermination_by 子句引入了终止参数。

Specify a termination measure for recursive functions.
```
termination_by a - b
```
indicates that termination of the currently defined recursive function follows
because the difference between the arguments `a` and `b` decreases.

If the function takes further argument after the colon, you can name them as follows:
```
def example (a : Nat) : Nat → Nat → Nat :=
termination_by b c => a - b
```

By default, a `termination_by` clause will cause the function to be constructed using well-founded
recursion. The syntax `termination_by structural a` (or `termination_by structural _ c => c`)
indicates the function is expected to be structural recursive on the argument. In this case
the body of the `termination_by` clause must be one of the function's parameters.

If omitted, a termination measure will be inferred. If written as `termination_by?`,
the inferred termination measure will be suggested.

terminationBy ::= ...
    | Specify a termination measure for recursive functions.
```
termination_by a - b
```
indicates that termination of the currently defined recursive function follows
because the difference between the arguments `a` and `b` decreases.

If the function takes further argument after the colon, you can name them as follows:
```
def example (a : Nat) : Nat → Nat → Nat :=
termination_by b c => a - b
```

By default, a `termination_by` clause will cause the function to be constructed using well-founded
recursion. The syntax `termination_by structural a` (or `termination_by structural _ c => c`)
indicates the function is expected to be structural recursive on the argument. In this case
the body of the `termination_by` clause must be one of the function's parameters.

If omitted, a termination measure will be inferred. If written as `termination_by?`,
the inferred termination measure will be suggested.

termination_by (ident* =>)? term

可选 => 之前的标识符可以将函数参数带入非 已经绑定在声明头中,并且强制术语必须指示函数的参数之一,无论是在头中引入还是在子句中本地引入。

Division by Iterated Subtraction

除法可以指定为从被除数中减去除数的次数。 无法使用结构递归详细说明此操作,因为减法不是模式匹配。 n 的值确实会随着每次递归调用而减小,因此良基递归可用于通过迭代减法来证明除法的定义合理。

def div (n k : Nat) : Nat := if k = 0 then 0 else if k > n then 0 else 1 + div (n - k) k termination_by n

7.6.3.1. 基础良好的关系🔗

如果不存在无限下降链,则关系 有充分基础的关系

x_0 ≻ x_1 ≻ \cdots

在 Lean 中,配备有规范基础关系的类型是 WellFoundedRelation 类型类的实例。

🔗type class
WellFoundedRelation.{u} (α : Sort u) : Sort (max 1 u)
WellFoundedRelation.{u} (α : Sort u) : Sort (max 1 u)

A type that has a standard well-founded relation.

Instances are used to prove that functions terminate using well-founded recursion by showing that recursive calls reduce some measure according to a well-founded relation. This relation can combine well-founded relations on the recursive function's parameters.

Instance Constructor

WellFoundedRelation.mk.{u}

Methods

rel : α  α  Prop

A well-founded relation on α.

wf : WellFounded WellFoundedRelation.rel

A proof that rel is, in fact, well-founded.

最重要的实例是:

  • Nat,由 (· < ·) 订购。

  • Prod,按字典顺序排序:(a₁, b₁) (a₂, b₂) 当且仅当 a₁ a₂a₁ = a₂b₁ b₂

  • 作为 SizeOf 类型类实例(提供方法 SizeOf.sizeOf)的每个类型都具有良好基础的关系。 对于这些类型,x₁ x₂ 当且仅当 sizeOf x₁ < sizeOf x₂。对于 归纳类型SizeOf 实例由 Lean 自动派生。

请注意,存在一个低优先级实例 instSizeOfDefault,它为任何类型提供 SizeOf 实例,并且始终返回 0。 此实例不能用于证明函数使用良基递归终止,因为 0 < 0 为 false。

Default Size Instance

一般来说,函数类型不具有对终止证明有用的有根据的关系。 实例综合因此选择instSizeOfDefault和相应的有根据的关系。 如果度量是函数,则选择默认的 SizeOf 实例,证明无法成功。

declaration uses `sorry`def declaration uses `sorry`fooInst (b : Bool Bool) : Unit := fooInst (b b) termination_by b decreasing_by b:Bool BoolsizeOf (b b) < sizeOf b b:Bool Bool0 < 0 b:Bool Bool0 < 0 b:Bool BoolFalse b:Bool BoolFalse All goals completed! 🐙

7.6.3.2. 终止证明🔗

一旦指定了 measure 并确定了其 well-founded relation,Lean 就会确定每个递归调用的终止证明义务。

每个递归调用的证明义务的形式为 g a₁ a₂ g p₁ p₂ ,其中:

  • g 是作为参数函数的测量值,

  • 是推断的有根据的关系,

  • a₁ a₂ 是递归调用的参数,

  • p₁ p₂ 是函数定义的参数。

证明义务的上下文是递归调用的本地上下文。 特别是,局部假设(例如 if h : _match h : _ with have 引入的假设)可用。 如果函数参数是模式匹配的 判别式(例如,通过 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 表达式),则该参数将被细化为证明义务中的匹配模式。

整体终止证明义务由每个递归调用的一个目标组成。 默认情况下,策略decreasing_trivial 用于证明每个证明义务。 可以使用可选的 Lean.Parser.Command.declaration : commanddecreasing_by 子句(位于 Lean.Parser.Command.declaration : commandtermination_by 子句之后)提供自定义策略脚本。 此策略脚本运行一次,每个证明义务都有一个目标,而不是针对每个证明义务单独运行。

Termination Proof Obligations

以下斐波那契数列的递归定义有两次递归调用,这导致终止证明中有两个目标。

def fib (n : Nat) := if h : n 1 then 1 else fib (n - 1) + fib (n - 2) termination_by n unsolved goals n:Nath:¬n 1n - 1 < n n:Nath:¬n 1n - 2 < ndecreasing_by n:Nath:¬n 1n - 1 < nn:Nath:¬n 1n - 2 < n
n:Nath:¬n 1n - 1 < nn:Nath:¬n 1n - 2 < n

这里,measure 只是参数本身,有根据的顺序是自然数上的小于关系。 第一个证明目标要求用户证明第一个递归调用的参数(即 n - 1)严格小于函数的参数 n

两种端接证明均可使用 omega策略轻松解除。

def fib (n : Nat) := if h : n 1 then 1 else fib (n - 1) + fib (n - 2) termination_by n decreasing_by n:Nath:¬n 1n - 1 < n All goals completed! 🐙 n:Nath:¬n 1n - 2 < n All goals completed! 🐙
Refined Parameters

如果函数的参数是模式匹配的 判别式,则证明义务会提到精炼参数。

def fib : Nat Nat | 0 | 1 => 1 | .succ (.succ n) => fib (n + 1) + fib n termination_by n => n unsolved goals n:Natn + 1 < n.succ.succ n:Natn < n.succ.succdecreasing_by n:Natn + 1 < n.succ.succn:Natn < n.succ.succ
n:Natn + 1 < n.succ.succn:Natn < n.succ.succ

此外,上下文还通过额外的假设得以丰富,可以更容易地证明终止。 一些例子包括:

  • if-then-else 表达式的分支中,添加了断言当前分支条件的假设,就像使用了依赖的 if-then-else 语法一样。

  • 在某些高阶函数的函数参数中,函数体的上下文通过有关参数的假设来丰富。

该列表并不详尽,并且该机制是可扩展的。 预处理部分中有详细描述。

Enriched Proof Obligation Contexts

这里,termIfThenElse : term`if c then t else e` is notation for `ite c t e`, "if-then-else", which decides to return `t` or `e` depending on whether `c` is true or false. The explicit argument `c : Prop` does not have any actual computational content, but there is an additional `[Decidable c]` argument synthesized by typeclass inference which actually determines how to evaluate `c` to true or false. Write `if h : c then t else e` instead for a "dependent if-then-else" `dite`, which allows `t`/`e` to use the fact that `c` is true/false. if 没有将关于条件(即,是否 n 1)的本地假设添加到分支中的本地上下文。

def fib (n : Nat) := if n 1 then 1 else fib (n - 1) + fib (n - 2) termination_by n unsolved goals n:Nath✝:¬n 1n - 1 < n n:Nath✝:¬n 1n - 2 < ndecreasing_by n:Nath✝:¬n 1n - 1 < nn:Nath✝:¬n 1n - 2 < n

尽管如此,这些假设在终止证明的上下文中是可用的:

n:Nath✝:¬n 1n - 1 < nn:Nath✝:¬n 1n - 2 < n

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. forLean.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. in 循环体中的终止证明义务也得到了丰富,在本例中具有 Std.Legacy.Range 成员资格假设:

def f (xs : Array Nat) : Nat := Id.run do let mut s := xs.sum for i in [:xs.size] do s := s + f (xs.take i) pure s termination_by xs unsolved goals xs:Array Nats:Nat := xs.sumi:Nath✝:i [:xs.size]sizeOf (xs.take i) < sizeOf xsdecreasing_by xs:Array Nats:Nat := xs.sumi:Nath✝:i [:xs.size]sizeOf (xs.take i) < sizeOf xs
xs:Array Nati:Nath✝:i [:xs.size]sizeOf (xs.take i) < sizeOf xs

类似地,在以下(人为的)示例中,终止证明包含一个附加假设,显示 x xs

def f (n : Nat) (xs : List Nat) : Nat := List.sum (xs.map (fun x => f x [])) termination_by xs unsolved goals n:Natxs:List Natx:Nath✝:x xssizeOf [] < sizeOf xsdecreasing_by n:Natxs:List Natx:Nath✝:x xssizeOf [] < sizeOf xs
n:Natxs:List Natx:Nath✝:x xssizeOf [] < sizeOf xs

此功能需要对嵌套递归调用的高阶函数进行特殊设置,如 预处理部分中所述。 在下面的定义中,除了使用自定义的等效函数而不是 List.map 之外,与上面的定义相同,证明义务上下文未得到丰富:

def List.myMap := @List.map def f (n : Nat) (xs : List Nat) : Nat := List.sum (xs.myMap (fun x => f x [])) termination_by xs unsolved goals n:Natxs:List Natx:NatsizeOf [] < sizeOf xsdecreasing_by n:Natxs:List Natx:NatsizeOf [] < sizeOf xs
n:Natxs:List Natx:NatsizeOf [] < sizeOf xs

7.6.3.3. 默认终止证明策略🔗

如果未给出 Lean.Parser.Command.declaration : commanddecreasing_by 子句,则隐式使用 decreasing_tactic,并分别应用于每个证明义务。

🔗tactic
decreasing_tactic

策略decreasing_tactic 主要处理元组的字典顺序,如果乘积的左侧组件是 定义等价,则应用 Prod.Lex.right,否则应用 Prod.Lex.left。 以这种方式预处理元组后,它调用 decreasing_trivial策略。

🔗tactic
decreasing_trivial

Extensible helper tactic for decreasing_tactic. This handles the "base case" reasoning after applying lexicographic order lemmas. It can be extended by adding more macro definitions, e.g.

macro_rules | `(tactic| decreasing_trivial) => `(tactic| linarith)

策略decreasing_trivial 是可扩展的策略,它应用一些常见的启发式方法来解决终止目标。 特别是,它尝试以下策略和定理:

  • simp_arith

  • assumption

  • 定理 Nat.sub_succ_lt_selfNat.pred_lt_of_ltNat.pred_lt,处理常见算术目标

  • omega

  • array_get_decarray_mem_dec,证明数组元素的大小小于数组的大小

  • sizeOf_list_dec 列表元素的大小小于列表的大小

  • String.Legacy.Iterator.sizeOf_next_lt_of_hasNextString.Legacy.Iterator.sizeOf_next_lt_of_atEnd,使用 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 处理字符串迭代

该策略旨在使用 Lean.Parser.Command.macro_rules : commandmacro_rules 进行进一步的启发式扩展。

No Backtracking of Lexicographic Order

需要更复杂的 measure 的递归函数的一个经典示例是 Ackermann 函数:

def ack : Nat Nat Nat | 0, n => n + 1 | m + 1, 0 => ack m 1 | m + 1, n + 1 => ack m (ack (m + 1) n) termination_by m n => (m, n)

该度量是一个元组,因此每个递归调用都必须针对按字典顺序小于参数的参数。 默认的 decreasing_tactic 可以处理这个问题。

特别要注意的是,第三个递归调用具有小于第二个参数的第二个参数和定义上等于第一个参数的第一个参数。 这允许 decreasing_tactic 申请 Prod.Lex.right

Prod.Lex.right {α β} {ra : α α Prop} {rb : β β Prop} (a : α) {b₁ b₂ : β} (h : rb b₁ b₂) : Prod.Lex ra rb (a, b₁) (a, b₂)

但是,使用以下修改后的函数定义会失败,其中第三个递归调用的第一个参数可证明小于或等于第一个参数,但在语法上不相等:

def synack : Nat Nat Nat | 0, n => n + 1 | m + 1, 0 => synack m 1 | m + 1, n + 1 => synack m (failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goal m n:Natm / 2 + 1 < m + 1synack (m / 2 + 1) n) termination_by m n => (m, n)
failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
m n:Natm / 2 + 1 < m + 1

由于Prod.Lex.right不适用,所以策略使用了Prod.Lex.left,从而导致上述目标无法证明。

此函数定义可能需要使用更通用的定理 Prod.Lex.right' 进行手动证明,该定理允许元组的第一个组件(必须为 Nat 类型)小于或等于而不是严格相等:

Prod.Lex.right' {β} (rb : β β Prop) {a₂ : Nat} {b₂ : β} {a₁ : Nat} {b₁ : β} (h₁ : a₁ a₂) (h₂ : rb b₁ b₂) : Prod.Lex Nat.lt rb (a₁, b₁) (a₂, b₂)def synack : Nat Nat Nat | 0, n => n + 1 | m + 1, 0 => synack m 1 | m + 1, n + 1 => synack m (synack (m / 2 + 1) n) termination_by m n => (m, n) decreasing_by m:NatProd.Lex (fun a₁ a₂ => a₁ < a₂) (fun a₁ a₂ => a₁ < a₂) (m, 1) (m.succ, 0) m:Natm < m.succ All goals completed! 🐙 -- the next goal corresponds to the third recursive call m:Natn:NatProd.Lex (fun a₁ a₂ => a₁ < a₂) (fun a₁ a₂ => a₁ < a₂) (m / 2 + 1, n) (m.succ, n.succ) m:Natn:Natm / 2 + 1 m.succm:Natn:Natn < n.succ m:Natn:Natm / 2 + 1 m.succ All goals completed! 🐙 m:Natn:Natn < n.succ All goals completed! 🐙 m:Natn:Natx✝:(y : (_ : Nat) ×' Nat) (invImage (fun x => PSigma.casesOn x fun a a_1 => (a, a_1)) Prod.instWellFoundedRelation).1 y m.succ, n.succ NatProd.Lex (fun a₁ a₂ => a₁ < a₂) (fun a₁ a₂ => a₁ < a₂) (m, x✝ m / 2 + 1, n ) (m.succ, n.succ) m:Natn:Natx✝:(y : (_ : Nat) ×' Nat) (invImage (fun x => PSigma.casesOn x fun a a_1 => (a, a_1)) Prod.instWellFoundedRelation).1 y m.succ, n.succ Natm < m.succ All goals completed! 🐙

decreasing_tactic策略不使用更强的 Prod.Lex.right',因为它需要在失败时回溯。

7.6.3.4. 推断良基递归🔗

如果递归函数定义未指示终止 measure,Lean 将尝试自动发现终止。 如果 Lean.Parser.Command.declaration : commandtermination_byLean.Parser.Command.declaration : commanddecreasing_by 均未提供,则 Lean 将在尝试良基递归之前尝试 推断结构递归。 如果存在 Lean.Parser.Command.declaration : commanddecreasing_by 子句,则仅尝试良基递归。

为了推断合适的终止 measure,Lean 考虑多个 basic TerminationMeasures(它们是类型 Nat 的终止测量),然后尝试这些测量的所有元组。

考虑的基本终止措施是:

  • 其类型具有非平凡 SizeOf 实例的所有参数

  • 每当递归调用的本地上下文假设类型为 e₁ < e₂e₁ ≤ e₂ 时,表达式 e₂ - e₁ ,其中 e₁e₂ 的类型为 Nat 并且仅取决于函数的参数。 此方法基于 Panagiotis Manolios and Daron Vroon, 2006. “Termination Analysis with Calling Context Graphs”. In Proceedings of the International Conference on Computer Aided Verification (CAV 2006). (LNCS 4144) 的工作。

  • 在相互组中,使用附加的基本措施来区分对组中其他函数的递归调用和对正在定义的函数的递归调用(详细信息请参见有关相互良基递归的部分

候选度量是基本度量或基本度量的元组。 如果任何候选措施允许通过终止证明策略(即 Lean.Parser.Command.declaration : commanddecreasing_by 指定的策略或 decreasing_trivial,如果没有 Lean.Parser.Command.declaration : commanddecreasing_by 子句)来解除所有证明义务,则选择任意此类候选措施作为自动终止措施。

termination_by? 子句导致显示推断的终止注释。 可以使用提供的建议或代码操作将其自动添加到源文件中。

为了避免尝试所有度量元组的组合爆炸,Lean 首先将所有 基本终止度量 制成表格,确定基本度量是递减、严格递减还是非递减。 递减测度至少对于一次递归调用来说较小,并且在任何递归调用中都不会增加,而严格递减测度对于所有递归调用都较小。 非递减度量是终止策略无法显示递减或严格递减的度量。 根据表选择合适的元组。此方法基于 Lukas Bulwahn, Alexander Krauss, and Tobias Nipkow, 2007. “Finding Lexicographic Orders for Termination Proofs in Isabelle/HOL”. In Proceedings of the International Conference on Theorem Proving in Higher Order Logics (TPHOLS 2007). (LNTCS 4732) 当找不到自动测量值时,此表会显示在错误消息中。

Termination failure

如果没有 Lean.Parser.Command.declaration : commandtermination_by 子句,Lean 会尝试推断良基递归的度量。 如果失败,则会打印上述表格。 在此示例中,Lean.Parser.Command.declaration : commanddecreasing_by 子句只是阻止 Lean 尝试结构递归;这使错误消息保持特定。

Could not find a decreasing measure. The basic measures relate at each recursive call as follows: (<, ≤, =: relation proved, ? all proofs failed, _: no proof attempted) n m l 1) 36:6-25 = = = 2) 37:6-23 = < _ 3) 38:6-23 < _ _ Please use `termination_by` to specify a decreasing measure.def f : (n m l : Nat) Nat | n+1, m+1, l+1 => [ f (n+1) (m+1) (l+1), f (n+1) (m-1) (l), f (n) (m+1) (l) ].sum | _, _, _ => 0 decreasing_by all_goals decreasing_tactic
Could not find a decreasing measure.
The basic measures relate at each recursive call as follows:
(<, ≤, =: relation proved, ? all proofs failed, _: no proof attempted)
           n m l
1) 36:6-25 = = =
2) 37:6-23 = < _
3) 38:6-23 < _ _
Please use `termination_by` to specify a decreasing measure.

这三个递归调用由它们的源位置来标识。 该消息传达了以下事实:

  • 在第一次递归调用中,所有参数(可证明)等于参数

  • 在第二个递归调用中,第一个参数等于第一个参数,并且第二个参数可证明小于第二个参数。 对于此递归调用,没有检查第三个参数,因为没有必要确定不存在合适的终止参数。

  • 在第三次递归调用中,第一个参数严格递减,其他参数不进行检查。

当终止证明以这种方式失败时,发现问题的一个好方法是使用 Lean.Parser.Command.declaration : commandtermination_by 显式指示预期的终止参数。 这将显示来自失败的策略的消息。

Array Indexing

e₂ - e₁ 形式的表达式视为度量的目的是支持计数到某个上限的常见习惯用法,特别是在以可能有趣的方式遍历数组时。 在以下对排序数组执行二分搜索的函数中,此启发式帮助 Lean 查找 j - i 度量。

def binarySearch (x : Int) (xs : Array Int) : Option Nat := go 0 xs.size where go (i j : Nat) (hj : j xs.size := by omega) := if h : i < j then let mid := (i + j) / 2 let y := xs[mid] if x = y then some mid else if x < y then go i mid else go (mid + 1) j else none Try this: [apply] termination_by (j, j - i)termination_by?

事实上,推断终止参数使用某种任意度量,而不是最佳或最小度量,这一事实在推断度量中可见,其中包含冗余的 j

Try this:
  [apply] termination_by (j, j - i)
Termination Proof Tactics During Inference

在推断终止 measure 时,Lean.Parser.Command.declaration : commanddecreasing_by 指示的策略的使用方式与实际终止证明中的使用方式略有不同。

  • 在推理过程中,它应用于单个目标,尝试在 Nat 上证明 <

  • 在终止证明期间,它应用于许多同时目标(每个递归调用一个),并且目标可能涉及对的字典顺序。

结果是,单独解决目标并使用显式终止参数成功工作的 Lean.Parser.Command.declaration : commanddecreasing_by 块可能会导致终止措施的推断失败:

Could not find a decreasing measure. The basic measures relate at each recursive call as follows: (<, ≤, =: relation proved, ? all proofs failed, _: no proof attempted) x1 x2 1) 643:16-23 ? ? 2) 644:27-40 _ _ 3) 644:20-41 _ _ Please use `termination_by` to specify a decreasing measure.def ack : Nat Nat Nat | 0, n => n + 1 | m + 1, 0 => ack m 1 | m + 1, n + 1 => ack m (ack (m + 1) n) decreasing_by · apply Prod.Lex.left omega · apply Prod.Lex.right omega · apply Prod.Lex.left omega

每当给出显式 Lean.Parser.Command.declaration : commanddecreasing_by 证明时,建议始终包含 Lean.Parser.Command.declaration : commandtermination_by 子句。

Inference too powerful

由于 decreasing_tactic 避免了由于字典排序不完整而需要回溯,因此 Lean 可能会推断出终止 measure,从而导致策略无法证明的目标。 在这种情况下,错误消息是由于策略失败而导致的错误消息,而不是由于无法找到度量而导致的错误消息。 这是 notAck 中发生的情况:

def notAck : Nat Nat Nat | 0, n => n + 1 | m + 1, 0 => notAck m 1 | m + 1, n + 1 => notAck m (notAck (m / 2 + 1) n) decreasing_by all_goals failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goal m n:Natm / 2 + 1 < m + 1All goals completed! 🐙
failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
m n:Natm / 2 + 1 < m + 1

在这种情况下,明确声明终止 measure 会有所帮助。

7.6.3.5. 相互良基递归🔗

Lean 支持使用 良基递归 定义 互递归函数。 可以使用 mutual block 引入相互递归,但它也可以由 Lean.Parser.Term.letrec : termlet rec 表达式和 Lean.Parser.Command.declaration : commandwhere 块产生。 相互良基递归的规则应用于一组实际相互递归、提升的定义,这些定义由相互组的 精化步骤 产生。

如果交互组中的任何函数具有 Lean.Parser.Command.declaration : commandtermination_byLean.Parser.Command.declaration : commanddecreasing_by 子句,则尝试使用良基递归。 如果使用 Lean.Parser.Command.declaration : commandtermination_by 为交互组中的 any 函数指定终止 measure,则该组中的 all 函数必须指定终止测量,并且它们必须具有相同的类型。

如果未指定终止参数,则终止参数为 推断,如上所述。在相互递归的情况下,在推理过程中考虑第三类基本度量,即对于相互组中的每个函数,该函数的度量为 1,其他函数的度量为 0。这允许 Lean 对函数进行排序,以便即使参数不减少,也允许从一个函数到另一个函数的某些调用。

Mutual recursion without parameter decrease

在以下互函数定义中,从 gf 的调用中参数不会减少。 尽管如此,由于附加基本措施对功能本身强加了顺序,因此该定义被接受。

mutual def f : (n : Nat) Nat | 0 => 0 | n + 1 => g n Try this: [apply] termination_by n => (n, 0)termination_by? def g (n : Nat) : Nat := (f n) + 1 Try this: [apply] termination_by (n, 1)termination_by? end

f 的推断终止参数为:

Try this:
  [apply] termination_by n => (n, 0)

g 的推断终止参数为:

Try this:
  [apply] termination_by (n, 1)

7.6.3.6. 预处理函数定义🔗

Lean 在确定每个调用站点的证明义务之前预处理函数的主体,将其转换为可能包含附加信息的等效定义。 此预处理步骤主要用于通过附加假设来丰富本地上下文,这些假设可能是解决终止证明义务所必需的,从而使用户无需手动执行等效转换。 预处理使用 简化器,并且可由用户扩展。

预处理分三个步骤进行:

  1. Lean 使用 wfParam gadget 注释函数参数或参数子项的出现。

    wfParam {α} (a : α) : α

    更准确地说,函数参数的每次出现都包含在 wfParam 中。 每当 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 表达式将 any 判别式包装在 wfParam 中时,该小工具就会被删除,并且每次出现的模式匹配变量(无论它是否来自 wfParam 小工具的判别式)都会包装在 wfParam 中。 wfParam 设备也从 投影功能 应用程序中浮出。

  2. 带注释的函数体使用 简化器 进行简化,仅使用 wf_preprocess 自定义 simp 集 中的简化规则。

  3. 最后,删除所有剩余的 wfParam 标记。

注释用于良基递归的函数参数允许预处理简化规则区分参数和其他术语。

attributePreprocessing Simp Set for Well-Founded Recursion
attr ::= ...
    | Theorems tagged with the `wf_preprocess` attribute are used during the processing of functions defined
by well-founded recursion. They are applied to the function's body to add additional hypotheses,
such as replacing `if c then _ else _` with `if h : c then _ else _` or `xs.map` with
`xs.attach.map`. Also see `wfParam`.

Warning: These rewrites are only applied to the declaration for the purpose of the logical
definition, but do not affect the compiled code. In particular they can cause a function definition
that diverges as compiled to be accepted without an explicit `partial` keyword, for example if they
remove irrelevant subterms or change the evaluation order by hiding terms under binders. Therefore
avoid tagging theorems with `[wf_preprocess]` unless they preserve also operational behavior.
wf_preprocess

Theorems tagged with the wf_preprocess attribute are used during the processing of functions defined by well-founded recursion. They are applied to the function's body to add additional hypotheses, such as replacing if c then _ else _ with if h : c then _ else _ or xs.map with xs.attach.map. Also see wfParam.

Warning: These rewrites are only applied to the declaration for the purpose of the logical definition, but do not affect the compiled code. In particular they can cause a function definition that diverges as compiled to be accepted without an explicit partial keyword, for example if they remove irrelevant subterms or change the evaluation order by hiding terms under binders. Therefore avoid tagging theorems with [wf_preprocess] unless they preserve also operational behavior.

🔗def
wfParam.{u} {α : Sort u} (a : α) : α
wfParam.{u} {α : Sort u} (a : α) : α

The wfParam gadget is used internally during the construction of recursive functions by wellfounded recursion, to keep track of the parameter for which the automatic introduction of List.attach (or similar) is plausible.

wf_preprocess simp 集中的一些重写规则通常适用,无需注意 wfParam 标记。 特别是,定理 ite_eq_dite 用于扩展 if-then-else 表达式分支的上下文,并带有有关条件的假设:此假设的名称应该是基于 h 的不可访问名称,如使用 binderNameHint 和术语所示()。活页夹名称提示在 策略语言参考中进行了描述。

ite_eq_dite {P : Prop} {α : Sort u} {a b : α} [Decidable P] : (if P then a else b) = if h : P then binderNameHint h () a else binderNameHint h () b

其他重写规则使用 wfParam 标记来限制其适用性;它们仅在将函数(如 List.map)应用于参数或参数的子项时使用,否则不使用。 这通常分两步完成:

  1. 诸如 List.map_wfParam 之类的定理识别对函数参数(或子项)的 List.map 调用,并使用 List.attach 来丰富列表元素的类型,断言它们确实是该列表的元素:

    List.map_wfParam (xs : List α) (f : α β) : (wfParam xs).map f = xs.attach.unattach.map f
  2. List.map_unattach 等定理使得该断言可用于 List.map 的函数参数。

    List.map_unattach (P : α Prop) (xs : List { x : α // P x }) (f : α β) : xs.unattach.map f = xs.map fun x, h => binderNameHint x f <| binderNameHint h () <| f (wfParam x)

    如果 f 是 lambda 表达式,则该定理使用 binderNameHint 小工具来保留用户选择的绑定程序名称。

通过将 List.attach 的引入与引入的假设的传播分开,即使在 (xs.reverse.filter p).map f 等链中,也可以为 f 提供所需的 x xs 假设。

可以通过将选项 wf.preprocess 设置为 false 来禁用此预处理。 要查看删除 wfParam 标记之前和之后的预处理函数定义,请将选项 trace.Elab.definition.wf 设置为 true

🔗option
trace.Elab.definition.wf

Default value: false

enable/disable tracing for the given module and submodules

Preprocessing for a custom data type

此示例演示了为自定义容器类型启用自动良基递归所需的条件。 结构类型 Pair 是同构对:它恰好包含两个元素,这两个元素都具有相同的类型。 它可以被认为类似于始终包含两个元素的列表或数组。

作为容器,Pair 可以支持 map 操作。 为了支持良基递归(其中递归调用发生在映射到 Pair 的函数体内),需要一些附加定义,包括成员资格谓词、将成员大小与包含对的大小相关联的定理、引入和消除有关成员资格的假设的帮助程序、插入这些帮助程序的 wf_preprocess 规则以及对decreasing_trivial策略。 这些步骤中的每一个都使得使用 Pair 变得更加容易,但没有一个步骤是绝对必要的;无需立即实施每种类型的所有步骤。

/-- A homogeneous pair -/ structure Pair (α : Type u) where fst : α snd : α /-- Mapping a function over the elements of a pair -/ def Pair.map (f : α β) (p : Pair α) : Pair β where fst := f p.fst snd := f p.snd

定义使用 Pair 的二叉树的嵌套归纳数据类型并尝试定义其 map 函数表明需要预处理规则。

/-- A binary tree defined using `Pair` -/ inductive Tree (α : Type u) where | leaf : α Tree α | node : Pair (Tree α) Tree α

map 函数的简单定义失败:

def Tree.map (f : α β) : Tree α Tree β | leaf x => leaf (f x) | node p => node (p.map (fun t' => failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goal α:Type u_1p:Pair (Tree α)t':Tree αsizeOf t' < 1 + sizeOf pt'.map f)) termination_by t => t
failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
α:Type u_1p:Pair (Tree α)t':Tree αsizeOf t' < 1 + sizeOf p

显然,证明义务是不可解决的,因为没有任何东西将 t'p 连接起来。

实现这种函数定义的标准习惯用法是拥有一个函数,通过证明它们实际上是集合的元素来丰富集合的每个元素。 陈述这个属性需要一个成员谓词。

inductive Pair.Mem (p : Pair α) : α Prop where | fst : Mem p p.fst | snd : Mem p p.snd instance : Membership α (Pair α) where mem := Pair.Mem

每个归纳类型自动具有一个 SizeOf 实例。 集合的元素应该小于集合,但必须先证明这一事实,然后才能使用它来构造终止证明:

theorem Pair.sizeOf_lt_of_mem {α} [SizeOf α] {p : Pair α} {x : α} (h : x p) : sizeOf x < sizeOf p := α:Type u_1inst✝:SizeOf αp:Pair αx:αh:x psizeOf x < sizeOf p α:Type u_1inst✝:SizeOf αp:Pair αsizeOf p.fst < sizeOf pα:Type u_1inst✝:SizeOf αp:Pair αsizeOf p.snd < sizeOf p α:Type u_1inst✝:SizeOf αp:Pair αsizeOf p.fst < sizeOf pα:Type u_1inst✝:SizeOf αp:Pair αsizeOf p.snd < sizeOf p α:Type u_1inst✝:SizeOf αfst✝:αsnd✝:αsizeOf { fst := fst✝, snd := snd✝ }.snd < sizeOf { fst := fst✝, snd := snd✝ } α:Type u_1inst✝:SizeOf αfst✝:αsnd✝:αsizeOf { fst := fst✝, snd := snd✝ }.fst < sizeOf { fst := fst✝, snd := snd✝ }α:Type u_1inst✝:SizeOf αfst✝:αsnd✝:αsizeOf { fst := fst✝, snd := snd✝ }.snd < sizeOf { fst := fst✝, snd := snd✝ } (α:Type u_1inst✝:SizeOf αfst✝:αsnd✝:α0 < 1 + sizeOf fst✝; All goals completed! 🐙)

下一步是定义 attachunattach 函数,通过证明它们是该对的元素来丰富该对的元素,或者删除所述证明。 这里,Pair.unattach 的类型更加通用,可以与任何 subtype 一起使用;这是一个典型的模式。

def Pair.attach (p : Pair α) : Pair {x : α // x p} where fst := p.fst, .fst snd := p.snd, .snd def Pair.unattach {P : α Prop} : Pair {x : α // P x} Pair α := Pair.map Subtype.val

现在可以通过使用 Pair.attachPair.sizeOf_lt_of_mem 显式定义 Tree.map

def Tree.map (f : α β) : Tree α Tree β | leaf x => leaf (f x) | node p => node (p.attach.map (fun t', _ => t'.map f)) termination_by t => t decreasing_by α:Type u_1p:Pair (Tree α)t':Tree αproperty✝:t' pthis:sizeOf t' < sizeOf psizeOf t' < sizeOf (node p) α:Type u_1p:Pair (Tree α)t':Tree αproperty✝:t' pthis:sizeOf t' < sizeOf psizeOf t' sizeOf p All goals completed! 🐙

这种转变可以完全自动化。 良基递归的预处理功能可用于自动引入 Pair.attach 函数。 这是分两个阶段完成的。 首先,当 Pair.map 应用于函数参数之一时,它将被重写为 attach/unattach 组合。 然后,当函数映射到 Pair.unattach 的结果时,函数将被重写以接受成员资格证明并将其纳入范围。

@[wf_preprocess] theorem Pair.map_wfParam (f : α β) (p : Pair α) : (wfParam p).map f = p.attach.unattach.map f := α:Type u_1β:Type u_2f:α βp:Pair αmap f (wfParam p) = map f p.attach.unattach α:Type u_1β:Type u_2f:α βfst✝:αsnd✝:αmap f (wfParam { fst := fst✝, snd := snd✝ }) = map f { fst := fst✝, snd := snd✝ }.attach.unattach All goals completed! 🐙 @[wf_preprocess] theorem Pair.map_unattach {P : α Prop} (p : Pair (Subtype P)) (f : α β) : p.unattach.map f = p.map fun x, Variable name `h` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`h => binderNameHint x f <| f (wfParam x) := α:Type u_1β:Type u_2P:α Propp:Pair (Subtype P)f:α βmap f p.unattach = map (fun x => match x with | x, h => binderNameHint x f (f (wfParam x))) p α:Type u_1β:Type u_2P:α Propf:α βfst✝:Subtype Psnd✝:Subtype Pmap f { fst := fst✝, snd := snd✝ }.unattach = map (fun x => match x with | x, h => binderNameHint x f (f (wfParam x))) { fst := fst✝, snd := snd✝ }; All goals completed! 🐙

现在可以在无需额外考虑的情况下编写函数体,并且成员资格假设仍然可用于终止证明。

def Tree.map (f : α β) : Tree α Tree β | leaf x => leaf (f x) | node p => node (p.map (fun t' => t'.map f)) termination_by t => t decreasing_by α:Type u_1p:Pair (Tree α)t':Tree αh:t' pthis:sizeOf t' < sizeOf psizeOf t' < sizeOf (node p) α:Type u_1p:Pair (Tree α)t':Tree αh:t' pthis:sizeOf t' < sizeOf psizeOf t' < 1 + sizeOf p All goals completed! 🐙

通过将 sizeOf_lt_of_mem 添加到 decreasing_trivial策略可以全自动进行证明,就像对类似的内置定理所做的那样。

macro "sizeOf_pair_dec" : tactic => `(tactic| with_reducible have := Pair.sizeOf_lt_of_mem _ omega done) macro_rules | `(tactic| decreasing_trivial) => `(tactic| sizeOf_pair_dec) def Tree.map (f : α β) : Tree α Tree β | leaf x => leaf (f x) | node p => node (p.map (fun t' => t'.map f)) termination_by t => t

为了使示例简短,sizeOf_pair_dec策略是针对这种特定的递归模式量身定制的,对于通用容器库来说还不够通用。 然而,它确实证明了库在实践中可以像标准库中的容器类型一样方便。

7.6.3.7. 理论与构建🔗

本节通过 良基递归 非常简要地介绍了终止证明背后的数学结构,这些数学结构有时可能会出现。 良基递归定义的函数精化基于 WellFounded.fix 运算符。

🔗def
WellFounded.fix.{u, v} {α : Sort u} {C : α Sort v} {r : α α Prop} (hwf : WellFounded r) (F : (x : α) ((y : α) r y x C y) C x) (x : α) : C x
WellFounded.fix.{u, v} {α : Sort u} {C : α Sort v} {r : α α Prop} (hwf : WellFounded r) (F : (x : α) ((y : α) r y x C y) C x) (x : α) : C x

A well-founded fixpoint. If satisfying the motive C for all values that are smaller according to a well-founded relation allows it to be satisfied for the current value, then it is satisfied for all values.

This function is used as part of the elaboration of well-founded recursion.

类型 α 使用函数的(变化的)参数进行实例化,并使用 PSigma 打包为一种类型。 WellFounded 关系是通过 invImage 从终结点 measure 构建的。

🔗def
invImage.{u_1, u_2} {α : Sort u_1} {β : Sort u_2} (f : α β) (h : WellFoundedRelation β) : WellFoundedRelation α
invImage.{u_1, u_2} {α : Sort u_1} {β : Sort u_2} (f : α β) (h : WellFoundedRelation β) : WellFoundedRelation α

The inverse image of a well-founded relation is well-founded.

The function's body is passed to WellFounded.fix, with parameters suitably packed and unpacked, and recursive calls are replaced with a call to the value provided by WellFounded.fix. Lean.Parser.Command.declaration : commanddecreasing_by策略生成的终止证明插入到正确的位置。

最后,由WellFounded.fix_eq证明了递归函数的方程和展开定理。 这些定理隐藏了打包和解包参数的细节,并根据原始定义描述了函数的行为。

在互递归的情况下,通过使用 PSum 组合函数的参数,并在结果类型和主体中对该和类型进行模式匹配,可以构造等效的非互函数。

WellFounded 的定义建立在关系的可访问元素的概念之上:

🔗inductive predicate
WellFounded.{u} {α : Sort u} (r : α α Prop) : Prop
WellFounded.{u} {α : Sort u} (r : α α Prop) : Prop

A relation r is WellFounded if all elements of α are accessible within r. If a relation is WellFounded, it does not allow for an infinite descent along the relation.

If the arguments of the recursive calls in a function definition decrease according to a well founded relation, then the function terminates. Well-founded relations are sometimes called Artinian or said to satisfy the “descending chain condition”.

Constructors

WellFounded.intro.{u} {α : Sort u} {r : α  α  Prop}
  (h :  (a : α), Acc r a) : WellFounded r

If all elements are accessible via r, then r is well-founded.

🔗inductive predicate
Acc.{u} {α : Sort u} (r : α α Prop) : α Prop
Acc.{u} {α : Sort u} (r : α α Prop) : α Prop

Acc is the accessibility predicate. Given some relation r (e.g. <) and a value x, Acc r x means that x is accessible through r:

x is accessible if there exists no infinite sequence ... < y₂ < y₁ < y₀ < x.

Constructors

Acc.intro.{u} {α : Sort u} {r : α  α  Prop} (x : α)
  (h :  (y : α), r y x  Acc r y) : Acc r x

A value is accessible if for all y such that r y x, y is also accessible. Note that if there exists no y such that r y x, then x is accessible. Such an x is called a base case.

Division by Iterated Subtraction: Termination Proof

迭代减法除法的定义可以使用良基递归显式编写。

noncomputable def div (n k : Nat) : Nat := (inferInstance : WellFoundedRelation Nat).wf.fix (fun n r => if h : k = 0 then 0 else if h : k > n then 0 else 1 + (r (n - k) <| α:Type un✝:Natk:Natn:Natr:(y : Nat) WellFoundedRelation.rel y n Nath✝:¬k = 0h:¬k > nWellFoundedRelation.rel (n - k) n α:Type un✝:Natk:Natn:Natr:(y : Nat) WellFoundedRelation.rel y n Nath✝:¬k = 0h:¬k > nn - k < n All goals completed! 🐙)) n

该定义必须标记为 Lean.Parser.Command.declaration : commandnoncomputable,因为编译器不支持良基递归。 与 recursors 一样,它是 Lean 逻辑的一部分。

除法的定义应满足以下方程:

  • {n k : Nat}, (k = 0) div n k = 0

  • {n k : Nat}, (k > n) div n k = 0

  • {n k : Nat}, (k 0) (¬ k > n) div n k = 1 + div (n - k) k

这种归约行为不支持 定义

theorem div.eq0 : div n 0 = 0 := n:Natdiv n 0 = 0 Tactic `rfl` failed: The left-hand side div n 0 is not definitionally equal to the right-hand side 0 n:Natdiv n 0 = 0n:Natdiv n 0 = 0
Tactic `rfl` failed: The left-hand side
  div n 0
is not definitionally equal to the right-hand side
  0

n:Natdiv n 0 = 0

然而,使用 WellFounded.fix_eq 展开良基递归,可以证明三个方程成立:

theorem div.eq0 : div n 0 = 0 := n:Natdiv n 0 = 0 n:Nat_proof_2.fix (fun n r => if h : 0 = 0 then 0 else if h : 0 > n then 0 else 1 + r (n - 0) ) n = 0 All goals completed! 🐙 theorem div.eq1 : k > n div n k = 0 := k:Natn:Natk > n div n k = 0 k:Natn:Nath:k > ndiv n k = 0 k:Natn:Nath:k > n_proof_2.fix (fun n r => if h : k = 0 then 0 else if h : k > n then 0 else 1 + r (n - k) ) n = 0 k:Natn:Nath:k > n(if h : k = 0 then 0 else if h : k > n then 0 else 1 + (fun y x => _proof_2.fix (fun n r => if h : k = 0 then 0 else if h : k > n then 0 else 1 + r (n - k) ) y) (n - k) ) = 0 k:Natn:Nath:k > n¬k = 0 k n 1 + _proof_2.fix (fun n r => if h : k = 0 then 0 else if h : n < k then 0 else 1 + r (n - k) ) (n - k) = 0 k:Natn:Nath:k > na✝¹:¬k = 0a✝:k n1 + _proof_2.fix (fun n r => if h : k = 0 then 0 else if h : n < k then 0 else 1 + r (n - k) ) (n - k) = 0; All goals completed! 🐙 theorem div.eq2 : ¬ k = 0 ¬ (k > n) div n k = 1 + div (n - k) k := k:Natn:Nat¬k = 0 ¬k > n div n k = 1 + div (n - k) k k:Natn:Nata✝¹:¬k = 0a✝:¬k > ndiv n k = 1 + div (n - k) k k:Natn:Nata✝¹:¬k = 0a✝:¬k > n_proof_2.fix (fun n r => if h : k = 0 then 0 else if h : k > n then 0 else 1 + r (n - k) ) n = 1 + _proof_2.fix (fun n r => if h : k = 0 then 0 else if h : k > n then 0 else 1 + r (n - k) ) (n - k) k:Natn:Nata✝¹:¬k = 0a✝:¬k > n(if h : k = 0 then 0 else if h : k > n then 0 else 1 + (fun y x => _proof_2.fix (fun n r => if h : k = 0 then 0 else if h : k > n then 0 else 1 + r (n - k) ) y) (n - k) ) = 1 + _proof_2.fix (fun n r => if h : k = 0 then 0 else if h : k > n then 0 else 1 + r (n - k) ) (n - k) k:Natn:Nata✝¹:¬k = 0a✝:k nn < k 0 = 1 + _proof_2.fix (fun n r => if h : n < k then 0 else 1 + r (n - k) ) (n - k) All goals completed! 🐙

7.6.4. 部分不动点递归🔗

所有定义从根本上来说都是方程:定义的新常数等于定义的右侧。 对于 结构递归 定义的函数,该等式保持 定义,并且该函数的应用返回一个唯一的值。 对于 良基递归 定义的函数,方程可能仅适用于 命题,但函数对参数的所有类型正确应用都等于定义规定的相应值。 在这两种情况下,函数对所有输入都终止的事实意味着通过应用函数计算的值始终是唯一确定的。

在某些情况下,如果函数并非针对所有参数都终止,则方程可能无法唯一地确定每个输入的函数返回值,但仍然存在定义方程成立的函数。 在这些情况下,定义为 partial fixpoint 仍然是可能的。 任何满足定义方程的函数都可以用来证明该方程不会产生逻辑矛盾,然后该方程可以被证明为关于该函数的定理。 与定义递归函数的其他策略一样,编译后的代码按最初编写的方式使用该函数;与消除器或可访问性证明的递归方面的定义类似,用于定义部分不动点的函数仅用于证明 Lean 逻辑中函数方程的合理性,以达到数学推理的目的。

术语 partial fixpoint 特定于 Lean。 声明为 Lean.Parser.Command.declaration : commandpartial 的函数不需要终止证明,只要其返回值的类型是固定的即可,但从 Lean 的逻辑角度来看,它们是完全不透明的。 另一方面,部分不动点可以在编写证明时使用其定义方程进行重写。 从逻辑上讲,部分不动点是应用时不会减少 定义 的总函数,但为其提供了等式重写规则。 它们是部分的,因为定义方程不一定为所有可能的参数指定一个值。

虽然部分固定点确实允许定义无法使用结构或良基递归表达的函数,但该技术在其他情况下也很有用。 即使在定义方程完全描述了函数的行为并且可以使用 良基递归 进行终止证明的情况下,将函数定义为部分固定点以避免编写终止证明可能会更方便。

仅当通过使用 Lean.Parser.Command.declaration : commandpartial_fixpoint 注释定义明确请求时,才会将递归函数定义为部分固定点。

有两类函数可以定义为部分不动点:

  • 返回类型为居住类型的尾递归函数

  • 以合适的 monad 形式返回值的函数,例如 Option monad

这两类都由相同的理论和构造支持:链完备偏序中单调方程的最小不动点。

正如结构函数和良基递归一样,Lean 允许将 相互递归 函数定义为部分固定点。 要使用此功能,mutual block 中的每个函数定义都必须使用 Lean.Parser.Command.declaration : commandpartial_fixpoint 修饰符进行注释。

Definition by Partial Fixpoint

以下函数查找谓词 p 成立的最小自然数。 如果 p 永远不成立,则此方程不指定行为:函数 find 可以返回 42 或任何其他 Nat 在这种情况下,并且仍然满足方程。

def find (p : Nat Bool) (i : Nat := 0) : Nat := if p i then i else find p (i + 1) partial_fixpoint

精化器可以证明满足方程的函数存在。 在 Lean 的逻辑中,find 被定义为任意此类函数。

7.6.4.1. 尾递归函数🔗

如果满足以下两个条件,则递归函数可以定义为部分不动点:

  1. 该函数的返回类型是固定的(与 标记为 Lean.Parser.Command.declaration : commandpartial 的函数一样)— NonemptyInhabited 实例都可以工作。

  2. 所有递归调用都在函数的 尾部位置 中。

如果表达式处于 尾部位置,则它是:

  • 函数体本身,

  • 位于尾部位置的 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 表达式的分支,

  • 位于尾部位置的 termIfThenElse : term`if c then t else e` is notation for `ite c t e`, "if-then-else", which decides to return `t` or `e` depending on whether `c` is true or false. The explicit argument `c : Prop` does not have any actual computational content, but there is an additional `[Decidable c]` argument synthesized by typeclass inference which actually determines how to evaluate `c` to true or false. Write `if h : c then t else e` instead for a "dependent if-then-else" `dite`, which allows `t`/`e` to use the fact that `c` is true/false. if 表达式的分支,以及

  • 位于尾部位置的 Lean.Parser.Term.let : term`let` is used to declare a local definition. Example: ``` let x := 1 let y := x + 1 x + y ``` Since functions are first class citizens in Lean, you can use `let` to declare local functions too. ``` let double := fun x => 2*x double (double 3) ``` For recursive definitions, you should use `let rec`. You can also perform pattern matching using `let`. For example, assume `p` has type `Nat × Nat`, then you can write ``` let (x, y) := p x + y ``` The *anaphoric let* `let := v` defines a variable called `this`. let 表达式的主体。

特别是,Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 表达式的 判别式termIfThenElse : term`if c then t else e` is notation for `ite c t e`, "if-then-else", which decides to return `t` or `e` depending on whether `c` is true or false. The explicit argument `c : Prop` does not have any actual computational content, but there is an additional `[Decidable c]` argument synthesized by typeclass inference which actually determines how to evaluate `c` to true or false. Write `if h : c then t else e` instead for a "dependent if-then-else" `dite`, which allows `t`/`e` to use the fact that `c` is true/false. if 表达式的条件和函数的参数不是尾部位置。

Loops are Tail Recursive Functions

因为函数体本身是一个尾部位置,所以无限循环函数loop是尾递归的。 它可以被定义为部分固定点。

def loop (x : Nat) : Nat := loop (x + 1) partial_fixpoint
Tail Recursion with Branching

Array.find 也可以使用具有终止证明的良基递归进行构造,但使用 Lean.Parser.Command.declaration : commandpartial_fixpoint 进行定义可能更方便,无需终止证明。

def Array.find (xs : Array α) (p : α Bool) (i : Nat := 0) : Option α := if h : i < xs.size then if p xs[i] then some xs[i] else Array.find xs p (i + 1) else none partial_fixpoint

如果递归调用的结果不只是返回,而是传递给另一个函数,则它不在尾部位置,并且此定义失败。

def List.findIndex (xs : List α) (p : α Bool) : Int := match xs with | [] => -1 | x::ys => if p x then 0 else have r := Could not prove 'List.findIndex' to be monotone in its recursive calls: Cannot eliminate recursive call `List.findIndex ys p` enclosed in if ys✝.findIndex p = -1 then -1 else ys✝.findIndex p + 1 Tried to apply 'monotone_ite', but failed. Possible cause: A missing `MonoBind` instance. Use `set_option trace.Elab.Tactic.monotonicity true` to debug.List.findIndex ys p if r = -1 then -1 else r + 1 partial_fixpoint

递归调用的错误消息是:

Could not prove 'List.findIndex' to be monotone in its recursive calls:
  Cannot eliminate recursive call `List.findIndex ys p` enclosed in
    if ys✝.findIndex p = -1 then -1 else ys✝.findIndex p + 1
  Tried to apply 'monotone_ite', but failed.
  Possible cause: A missing `MonoBind` instance.
  Use `set_option trace.Elab.Tactic.monotonicity true` to debug.

7.6.4.2. 一元函数🔗

如果函数的返回类型是作为 Lean.Order.MonoBind 实例的 monad(例如 Option),则将函数定义为部分固定点会更强大。 在这种情况下,递归调用不仅限于尾部位置,还可能发生在高阶一元函数内部,例如 bindList.mapM

其适用的高阶函数集是 extensible,因此这里没有给出详尽的列表。 我们的愿望是接受使用 bind 等抽象单子操作构建的单子递归函数定义,但不会打开单子的抽象(例如,通过匹配 Option 值)。 特别是,使用 Lean.Parser.Term.do : termdo-notation 应该可以。

Monadic functions

以下函数在 Option monad 中实现 Ackermann 函数,并且无需(显式或隐式)终止证明即可接受:

def ack : (n m : Nat) Option Nat | 0, y => some (y+1) | x+1, 0 => ack x 1 | x+1, y+1 => do ack x ( ack (x+1) y) partial_fixpoint

递归调用也可能发生在高阶函数中,例如 List.mapM(如果设置正确)和 Lean.Parser.Term.do : termdo-notation:

structure Tree where cs : List Tree def Tree.rev (t : Tree) : Option Tree := do Tree.mk ( t.cs.reverse.mapM (Tree.rev ·)) partial_fixpoint def Tree.rev' (t : Tree) : Option Tree := do let mut cs := [] for c in t.cs do cs := ( c.rev') :: cs return Tree.mk cs partial_fixpoint

递归调用结果上的模式匹配将阻止部分固定点的定义通过:

def List.findIndex (xs : List α) (p : α Bool) : Option Nat := match xs with | [] => none | x::ys => if p x then some 0 else match Could not prove 'List.findIndex' to be monotone in its recursive calls: Cannot eliminate recursive call `List.findIndex ys p` enclosed in match ys✝.findIndex p with | none => none | some r => some (r + 1) List.findIndex ys p with | none => none | some r => some (r + 1) partial_fixpoint
Could not prove 'List.findIndex' to be monotone in its recursive calls:
  Cannot eliminate recursive call `List.findIndex ys p` enclosed in
    match ys✝.findIndex p with
    | none => none
    | some r => some (r + 1)
  

在这种特殊情况下,使用 Functor.map 而不是显式模式匹配有助于:

def List.findIndex (xs : List α) (p : α Bool) : Option Nat := match xs with | [] => none | x::ys => if p x then some 0 else (· + 1) <$> List.findIndex ys p partial_fixpoint

7.6.4.3. 部分正确性定理🔗

对于定义为部分不动点的每个函数,Lean 证明满足定义方程。 这使得可以通过重写来证明。 然而,这些方程定理不足以推理函数在函数规范未终止的参数上的行为。 在运行时导致无限递归的代码路径最终将成为潜在证明中的无限重写链。

另一方面,合适的单子中的部分固定点提供了额外的定理,将未定义的值从非终止映射到单子中的合适的值。 在 Option 单子中,对于定义方程指定非终止的所有函数输入,部分固定点等于 Option.none。 根据这一事实,Lean 证明了函数的 部分正确性定理,该定理允许当函数结果为 Option.some 时得出事实。

Partial Correctness Theorem

回想一下之前示例中的 List.findIndex

def List.findIndex (xs : List α) (p : α Bool) : Option Nat := match xs with | [] => none | x::ys => if p x then some 0 else (· + 1) <$> List.findIndex ys p partial_fixpoint

通过此函数定义,Lean 自动证明以下部分正确性定理:

List.findIndex.partial_correctness.{u_1} {α : Type u_1} (p : α Bool) (motive : List α Nat Prop) (h : (findIndex : List α Option Nat), ( (xs : List α) (r : Nat), findIndex xs = some r motive xs r) (xs : List α) (r : Nat), (match xs with | [] => none | x :: ys => if p x = true then some 0 else (fun x => x + 1) <$> findIndex ys) = some r motive xs r) (xs : List α) (r : Nat) : xs.findIndex p = some r motive xs r

这里,动机是 List.findIndex 的参数和返回类型之间的关系,其中 Option 从返回类型中删除。 如果给定具有与 List.findIndex 兼容的签名的任意部分函数,则以下内容成立:

  • 对于任意函数返回值(而不是 none)的所有输入,动机都得到满足,

  • 使用定义方程进行一次重写,其中递归调用被任意函数替换,也意味着动机的满足

那么对于 List.findIndex 返回 some 的所有输入,动机都得到满足。

部分正确性定理是一个推理原理。 它可用于证明结果数字是列表中的有效索引,并且谓词适用于该索引:

theorem List.findIndex_implies_pred (xs : List α) (p : α Bool) : xs.findIndex p = some i x, xs[i]? = some x p x := α:Type u_1i:Natxs:List αp:α Boolxs.findIndex p = some i x, xs[i]? = some x p x = true α:Type u_1i:Natxs:List αp:α Bool (findIndex : List α Option Nat), (∀ (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = true) (xs : List α) (r : Nat), (match xs with | [] => none | x :: ys => if p x = true then some 0 else (fun x => x + 1) <$> findIndex ys) = some r x, xs[r]? = some x p x = true α:Type u_1i:Natxs✝:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truexs:List αr:Nathsome:(match xs with | [] => none | x :: ys => if p x = true then some 0 else (fun x => x + 1) <$> findIndex ys) = some r x, xs[r]? = some x p x = true α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αhsome:none = some r x, [][r]? = some x p x = trueα:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx✝:αys✝:List αhsome:(if p x✝ = true then some 0 else (fun x => x + 1) <$> findIndex ys✝) = some r x, (x✝ :: ys✝)[r]? = some x p x = true next α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αhsome:none = some r x, [][r]? = some x p x = true All goals completed! 🐙 next x ys α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx:αys:List αhsome:(if p x✝ = true then some 0 else (fun x => x + 1) <$> findIndex ys✝) = some r x, (x✝ :: ys✝)[r]? = some x p x = true α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx:αys:List αh✝:p x = truehsome:some 0 = some r x, (x✝ :: ys✝)[r]? = some x p x = trueα:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx:αys:List αh✝:¬p x = truehsome:(fun x => x + 1) <$> findIndex ys = some r x, (x✝ :: ys✝)[r]? = some x p x = true next α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx:αys:List αh✝:p x = truehsome:some 0 = some r x, (x✝ :: ys✝)[r]? = some x p x = true α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx:αys:List αh✝:p x = truehsome:some 0 = some rthis:r = 0 x_1, (x :: ys)[r]? = some x_1 p x_1 = true All goals completed! 🐙 next α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx:αys:List αh✝:¬p x = truehsome:(fun x => x + 1) <$> findIndex ys = some r x, (x✝ :: ys✝)[r]? = some x p x = true α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truer:Natxs✝:List αx:αys:List αh✝:¬p x = truehsome: a, findIndex ys = some a a + 1 = r x, (x✝ :: ys✝)[r]? = some x p x = true α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natih: (xs : List α) (r : Nat), findIndex xs = some r x, xs[r]? = some x p x = truexs✝:List αx:αys:List αh✝:¬p x = truer':Nathr:findIndex ys = some r' x_1, (x :: ys)[r' + 1]? = some x_1 p x_1 = true α:Type u_1i:Natxs:List αp:α BoolfindIndex:List α Option Natxs✝:List αx:αys:List αh✝:¬p x = truer':Natih: x, ys[r']? = some x p x = truehr:findIndex ys = some r' x_1, (x :: ys)[r' + 1]? = some x_1 p x_1 = true All goals completed! 🐙

7.6.4.4. 具有部分不动点的互递归🔗

Lean 支持使用 部分固定点 定义 相互递归 函数。 可以使用 mutual block 引入相互递归,但它也可以由 Lean.Parser.Term.letrec : termlet rec 表达式和 Lean.Parser.Command.declaration : commandwhere 块产生。 相互良基递归的规则应用于一组实际相互递归、提升的定义,这些定义由相互组的 精化步骤 产生。

如果交互组中的所有函数都有 Lean.Parser.Command.declaration : commandpartial_fixpoint 子句,则使用此策略。

7.6.4.5. 理论与构建🔗

该构造建立在克纳斯特-塔斯基定理的变体之上:在链完备偏序中,每个单调函数都有一个最小不动点。

必要的理论可以在 Lean.Order 命名空间中找到。 这并不是一个通用的阶次理论结果库。 相反,Lean.Order 中的定义和定理仅用作 Lean.Parser.Command.declaration : commandpartial_fixpoint 功能的实现细节,并且它们应被视为私有 API,可能会更改,恕不另行通知。

偏序的概念和链完全偏序的概念分别由类型类 Lean.Order.PartialOrderLean.Order.CCPO 表示。

🔗type class
Lean.Order.PartialOrder.{u} (α : Sort u) : Sort (max 1 u)
Lean.Order.PartialOrder.{u} (α : Sort u) : Sort (max 1 u)

A partial order is a reflexive, transitive and antisymmetric relation.

This is intended to be used in the construction of partial_fixpoint, and not meant to be used otherwise.

Instance Constructor

Lean.Order.PartialOrder.mk.{u}

Methods

rel : α  α  Prop

A “less-or-equal-to” or “approximates” relation.

This is intended to be used in the construction of partial_fixpoint, and not meant to be used otherwise.

rel_refl :  {x : α}, x  x

The “less-or-equal-to” or “approximates” relation is reflexive.

rel_trans :  {x y z : α}, x  y  y  z  x  z

The “less-or-equal-to” or “approximates” relation is transitive.

rel_antisymm :  {x y : α}, x  y  y  x  x = y

The “less-or-equal-to” or “approximates” relation is antisymmetric.

🔗type class
Lean.Order.CCPO.{u} (α : Sort u) : Sort (max 1 u)
Lean.Order.CCPO.{u} (α : Sort u) : Sort (max 1 u)

A chain-complete partial order (CCPO) is a partial order where every chain has a least upper bound.

This is intended to be used in the construction of partial_fixpoint, and not meant to be used otherwise.

Instance Constructor

Lean.Order.CCPO.mk.{u}

Extends

Methods

rel : α  α  Prop
Inherited from
  1. PartialOrder α
rel_refl :  {x : α}, x  x
Inherited from
  1. PartialOrder α
rel_trans :  {x y z : α}, x  y  y  z  x  z
Inherited from
  1. PartialOrder α
rel_antisymm :  {x y : α}, x  y  y  x  x = y
Inherited from
  1. PartialOrder α
has_csup :  {c : α  Prop}, chain c  Exists (is_sup c)

The least upper bound of chains exists.

如果函数保留偏序,则该函数是单调的。 也就是说,如果 x y,则 f x f y。 运算符 代表 Lean.Order.PartialOrder.rel

🔗def
Lean.Order.monotone.{u, v} {α : Sort u} [PartialOrder α] {β : Sort v} [PartialOrder β] (f : α β) : Prop
Lean.Order.monotone.{u, v} {α : Sort u} [PartialOrder α] {β : Sort v} [PartialOrder β] (f : α β) : Prop

A function is monotone if it maps related elements to related elements.

This is intended to be used in the construction of partial_fixpoint, and not meant to be used otherwise.

单调函数的不动点可以使用 fix 来获取,它确实构造了一个不动点,如 fix_eq 所示,

🔗def
Lean.Order.fix.{u} {α : Sort u} [CCPO α] (f : α α) (hmono : monotone f) : α
Lean.Order.fix.{u} {α : Sort u} [CCPO α] (f : α α) (hmono : monotone f) : α

The least fixpoint of a monotone function is the least upper bound of its transfinite iteration.

The monotone f assumption is not strictly necessarily for the definition, but without this the definition is not very meaningful and it simplifies applying theorems like fix_eq if every use of fix already has the monotonicity requirement.

This is intended to be used in the construction of partial_fixpoint, and not meant to be used otherwise.

🔗theorem
Lean.Order.fix_eq.{u} {α : Sort u} [CCPO α] {f : α α} (hf : monotone f) : fix f hf = f (fix f hf)
Lean.Order.fix_eq.{u} {α : Sort u} [CCPO α] {f : α α} (hf : monotone f) : fix f hf = f (fix f hf)

The main fixpoint theorem for fixed points of monotone functions in chain-complete partial orders.

This is intended to be used in the construction of partial_fixpoint, and not meant to be used otherwise.

为了构造部分固定点,Lean 首先合成合适的 CCPO 实例。

  • 如果函数的结果类型具有专用实例(如 OptioninstCCPOOption),则该实例与函数类型 instCCPOPi 的实例一起使用,以构造整个函数类型的实例。

  • 否则,如果可以显示该函数的类型由见证者 w 占据,则使用包装器类型 FlatOrder w 的实例 FlatOrder.instCCPO。在这个顺序中,w 是最小元素,所有其他元素都无法比较。

接下来,对函数定义右侧的递归调用进行抽象;这变成了 fix 的参数 f。单调性要求由 monotonicity策略解决,它以语法驱动的方式应用组合单调性引理。

策略使用以下步骤解决 monotone (fun x => x ) 形式的目标:

  • 当不存在对 x 的依赖时应用 monotone_const

  • 根据 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 表达式进行拆分。

  • 根据 termIfThenElse : term`if c then t else e` is notation for `ite c t e`, "if-then-else", which decides to return `t` or `e` depending on whether `c` is true or false. The explicit argument `c : Prop` does not have any actual computational content, but there is an additional `[Decidable c]` argument synthesized by typeclass inference which actually determines how to evaluate `c` to true or false. Write `if h : c then t else e` instead for a "dependent if-then-else" `dite`, which allows `t`/`e` to use the fact that `c` is true/false. if 表达式进行拆分。

  • 如果值和类型不依赖于 x,则将 Lean.Parser.Term.let : term`let` is used to declare a local definition. Example: ``` let x := 1 let y := x + 1 x + y ``` Since functions are first class citizens in Lean, you can use `let` to declare local functions too. ``` let double := fun x => 2*x double (double 3) ``` For recursive definitions, you should use `let rec`. You can also perform pattern matching using `let`. For example, assume `p` has type `Nat × Nat`, then you can write ``` let (x, y) := p x + y ``` The *anaphoric let* `let := v` defines a variable called `this`. let 表达式移至上下文。

  • 当值和类型确实依赖于 x 时,对 Lean.Parser.Term.let : term`let` is used to declare a local definition. Example: ``` let x := 1 let y := x + 1 x + y ``` Since functions are first class citizens in Lean, you can use `let` to declare local functions too. ``` let double := fun x => 2*x double (double 3) ``` For recursive definitions, you should use `let rec`. You can also perform pattern matching using `let`. For example, assume `p` has type `Nat × Nat`, then you can write ``` let (x, y) := p x + y ``` The *anaphoric let* `let := v` defines a variable called `this`. let 表达式进行 Zeta 缩减。

  • 应用用 partial_fixpoint_monotone 注释的引理

以下单调性引理已注册,并且应允许在 · 指示的参数中的给定高阶函数下进行递归调用(但不允许其他参数,如 _ 所示)。

Theorem

Pattern

monotone_allM

Array.allM · _ _ _

monotone_anyM

Array.anyM · _ _ _

monotone_anyM_loop

Array.anyM.loop · _ _ _

monotone_array_filterMapM

Array.filterMapM · _

monotone_array_forM

Array.forM · _ _ _

monotone_array_forRevM

Array.forRevM · _ _ _

monotone_findIdxM?

Array.findIdxM? · _

monotone_findM?

Array.findM? · _

monotone_findRevM?

Array.findRevM? · _

monotone_findSomeM?

Array.findSomeM? · _

monotone_findSomeRevM?

Array.findSomeRevM? · _

monotone_flatMapM

Array.flatMapM · _

monotone_foldlM

Array.foldlM · _ _ _ _

monotone_foldlM_loop

Array.foldlM.loop · _ _ _ _ _

monotone_foldrM

Array.foldrM · _ _ _ _

monotone_foldrM_fold

Array.foldrM.fold · _ _ _ _

monotone_forIn

forIn _ _ ·

monotone_forIn'

forIn' _ _ ·

monotone_forIn'_loop

Array.forIn'.loop _ · _ _

monotone_mapFinIdxM

_.mapFinIdxM ·

monotone_mapM

Array.mapM · _

monotone_modifyM

_.modifyM _ ·

monotone_map

_ <$> ·

monotone_allM

List.allM · _

monotone_anyM

List.anyM · _

monotone_filterAuxM

List.filterAuxM · _ _

monotone_filterM

List.filterM · _

monotone_filterRevM

List.filterRevM · _

monotone_findM?

List.findM? · _

monotone_findSomeM?

List.findSomeM? · _

monotone_foldlM

List.foldlM · _ _

monotone_foldrM

List.foldrM · _ _

monotone_forIn

forIn _ _ ·

monotone_forIn'

forIn' _ _ ·

monotone_forIn'_loop

List.forIn'.loop _ · _ _

monotone_forM

_.forM ·

monotone_mapM

List.mapM · _

monotone_bindM

Option.bindM · _

monotone_elimM

Option.elimM · · ·

monotone_getDM

_.getDM ·

monotone_mapM

Option.mapM · _

monotone_fst

·.fst

monotone_mk

·, ·

monotone_snd

·.snd

monotone_seq

· <*> ·

monotone_seqLeft

· <* ·

monotone_seqRight

· *> ·

coind_impl

· ·

coind_monotone_and

· ·

coind_monotone_exists

Exists ·

coind_monotone_forall

(y : _), _ _ _

coind_monotone_or

· ·

coind_not

¬·

implication_order_monotone_and

· ·

implication_order_monotone_exists

Exists ·

implication_order_monotone_forall

(y : _), _ _ _

implication_order_monotone_or

· ·

ind_impl

· ·

ind_not

¬·

monotone_bind

· >>= ·

monotone_dite

dite _ · ·

monotone_exceptTRun

·.run

monotone_ite

if _ then · else ·

monotone_optionTRun

·.run

monotone_readerTRun

·.run _

monotone_stateRefT'Run

·.run _

monotone_stateTRun

·.run _

这里描述的顺序理论框架也支持 共归纳和归纳谓词。 对于 Prop 值函数,Lean.Order.CompleteLattice 实例提供最小和最大固定点,从而支持使用 Lean.Parser.Command.declaration : commandinductive_fixpointLean.Parser.Command.declaration : commandcoinductive_fixpoint 子句进行定义。

7.6.5. 共归纳谓词和归纳谓词🔗

Lean 的 类型论 不直接支持共感类型。 然而,共归纳谓词,即Prop中的递归定义,可以使用命题上的完整格结构来定义。 这些谓词提供了共归纳推理原则,其中可以通过表明某事物满足一些本身与共归纳谓词的定义一致的较小谓词来证明它满足谓词。 这是归纳推理的对偶,其中可以通过潜在的递归案例分析来分解已知事实。 共归纳谓词允许指定和推理无限的域。 计算机科学的一些例子包括:

  • 允许循环的状态转换系统的相似性

  • 小步操作语义的分歧

  • 活性特性

同时,归纳谓词也可以使用相同的机制通过最少的固定点来定义。 由于它使用相同的底层机制,因此普通 归纳类型 的替代方案与混合感应-共感应互块兼容。

Infinite Sequences

给定 α 上的关系 R(即,类型为 α α Prop),则 α 中存在从 x 开始的无限序列值,如果:

  • 存在一些 y 使得 R x y,并且

  • 存在来自 y 的无限序列。

这是一个典型的共归纳谓词:它描述了一种潜在的无限行为,可以表示为没有基例的单个推理规则。

这个递归规范是明确定义的,但是它不能被定义为普通的递归函数,因为定义的递归部分并没有减少。 然而,这是一个完全合理的共归纳定义:

coinductive InfSeq (R : α α Prop) : α Prop where | step (y : α) : R x y InfSeq R y InfSeq R x

共归纳推理原理采用谓词 pred。 为了证明 a 是无限 R 序列的开始,只需证明 R 将满足 pred 的每个元素与其他此类元素相关即可。 换句话说,无限序列的存在可以通过提供一个来证明:

InfSeq.coinduct (R : α α Prop) (pred : α Prop) : ( (a : α), pred a y, R a y pred y) (a : α), pred a InfSeq R a

在 Lean 中有两种定义共归纳谓词的方法:

  1. Prop 中的递归 Lean.Parser.Command.declaration : commanddef 使用 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 终止子句,该子句采用最大的固定点。同样,Lean.Parser.Command.declaration : commandinductive_fixpoint 子句将归纳谓词定义为最小固定点。

  2. 使用 Lean.Parser.Command.coinductivecoinductive 命令,该命令提供镜像 Lean.Parser.Command.inductiveIn Lean, every concrete type other than the universes and every type constructor other than dependent arrows is an instance of a general family of type constructions known as inductive types. It is remarkable that it is possible to construct a substantial edifice of mathematics based on nothing more than the type universes, dependent arrow types, and inductive types; everything else follows from those. Intuitively, an inductive type is built up from a specified list of constructors. For example, `List α` is the list of elements of type `α`, and is defined as follows: ``` inductive List (α : Type u) where | nil | cons (head : α) (tail : List α) ``` A list of elements of type `α` is either the empty list, `nil`, or an element `head : α` followed by a list `tail : List α`. See [Inductive types](https://lean-lang.org/theorem_proving_in_lean4/inductive_types.html) for more information. inductive 声明的声明性语法。

7.6.5.1. 定点终止条款🔗

递归 Prop 值函数可以定义为不动点,方法是使用 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 进行共归纳定义(最大不动点)或 Lean.Parser.Command.declaration : commandinductive_fixpoint 进行归纳定义(最小不动点)进行注释。 这些终止子句的作用与 Lean.Parser.Command.declaration : commandpartial_fixpoint 相同,但使用 Prop 上的完整晶格结构来计算适当的固定点。

7.6.5.1.1. 共导固定点🔗

Lean.Parser.Command.declaration : commandcoinductive_fixpoint 子句将谓词定义为其定义方程的最大不动点。 该函数相对于 Lean.Order.ReverseImplicationOrder 必须是单调的,其中 P Q 表示 Q P

此排序在谓词域上逐点扩展。 给定谓词 PQ over αP Q 表示 x : α, P x Q x(即 x, Q x P x)。

Monotonicity of Infinite Sequences

当存在从 a 开始的 R 相关元素的无限链时,命题 InfSeq R a 为真。 可以使用 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 编写:

def InfSeq (R : α α Prop) (a : α) : Prop := b, R a b InfSeq R b coinductive_fixpoint

在精化期间,第一步是通过递归调用抽象此递归定义,产生与 F 等效的定义:

def F (R : α α Prop) (a : α) (P : α Prop) : Prop := b, R a b P b

为了使该函数在反向蕴涵方面是单调的,它必须保留 PQ 之间的反向蕴涵排序。 也就是说, (x : α), Q x P x 必须隐含 (x : α), F R x Q F R x P

theorem F_monotone (h : (x : α), Q x P x) : (x : α), F R x Q F R x P := α:Sort u_1R:α α PropQ:α PropP:α Proph: (x : α), Q x P x (x : α), F R x Q F R x P All goals completed! 🐙
Failure of Monotonicity

如果不存在通向该元素的无限链,则该元素在关系中是可访问的。 该属性在标准库中归纳定义为 Acc。 这种共推定义它的尝试失败了:

Could not prove 'NoInfChain' to be monotone in its recursive calls: Cannot eliminate recursive call in NoInfChain R y✝ def NoInfChain (R : α α Prop) (x : α) : Prop := y, R x y ¬NoInfChain R y coinductive_fixpoint
Could not prove 'NoInfChain' to be monotone in its recursive calls:
  Cannot eliminate recursive call in
    NoInfChain R y✝
  

对应的函数是:

def F (R : α α Prop) (x : α) (P : α Prop) : Prop := y, R x y ¬P y

Lean 未能证明这个函数是单调的,因为事实上它并不是单调的:

theorem F_nonmonotone : ¬( α R P Q, ( (x : α), Q x P x) ( (x : α), F R x Q F R x P)) := ¬ (α : Sort u_1) (R : α α Prop) (P Q : α Prop), (∀ (x : α), Q x P x) (x : α), F R x Q F R x P α R P Q, ¬((∀ (x : α), Q x P x) (x : α), F R x Q F R x P) -- α = PUnit, R always true P Q, ¬((∀ (x : PUnit), Q x P x) (x : PUnit), F (fun x x_1 => True) x Q F (fun x x_1 => True) x P) -- P is trivially true, Q is always false ¬((∀ (x : PUnit), (fun x => False) x (fun x => True) x) (x : PUnit), (F (fun x x_1 => True) x fun x => False) F (fun x x_1 => True) x fun x => True) All goals completed! 🐙
Non-Predicates

某个命题的无限合取可以定义为共归纳不动点:

def InfConj (p : Prop) : Prop := p InfConj p coinductive_fixpoint

然而,这不能用于定义无限乘积:

def InfProd (α : Type) : Prop := α × Application type mismatch: The argument InfProd α has type Prop of sort `Type` but is expected to have type Type ?u.3 of sort `Type (?u.3 + 1)` in the application α × InfProd αInfProd α unused `coinductive_fixpoint`, function is not recursivecoinductive_fixpoint

错误消息表明需要一个提议:

Application type mismatch: The argument
  InfProd α
has type
  Prop
of sort `Type` but is expected to have type
  Type ?u.3
of sort `Type (?u.3 + 1)` in the application
  α × InfProd α

正如通过部分不动点的定义一样,共归纳谓词的定义方程在定义上并不成立。 然而,精化器证明了等式引理,允许将谓词重写为展开式。

Definitional Equality and Coinductive Predicates

InfSeq 是关系开始无限链的共归纳语句:

def InfSeq (R : α α Prop) (a : α) : Prop := b, R a b InfSeq R b coinductive_fixpoint

因为它是使用 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 定义的,所以它在定义上并不等于其展开:

example (R : α α Prop) (a : α) : InfSeq R a = b, R a b InfSeq R b := α:Sort u_1R:α α Propa:αInfSeq R a = b, R a b InfSeq R b Tactic `rfl` failed: The left-hand side InfSeq R a is not definitionally equal to the right-hand side b, R a b InfSeq R b α:Sort u_1R:α α Propa:αInfSeq R a = b, R a b InfSeq R bα:Sort u_1R:α α Propa:αInfSeq R a = b, R a b InfSeq R b
Tactic `rfl` failed: The left-hand side
  InfSeq R a
is not definitionally equal to the right-hand side
   b, R a b  InfSeq R b

α:Sort u_1R:α  α  Propa:αInfSeq R a =  b, R a b  InfSeq R b

然而,它配备了等式引理,可以将其重写为展开式:

example (R : α α Prop) (a : α) : InfSeq R a = b, R a b InfSeq R b := α:Sort u_1R:α α Propa:αInfSeq R a = b, R a b InfSeq R b All goals completed! 🐙

除了方程引理之外,Lean 还生成 共归纳原理。 共归纳原理指出,共归纳谓词可以通过展示一些其他谓词来证明,这些谓词是单调函数的后固定点。

Coinduction Principles for Infinite Sequences

InfSeq 是关系开始无限链的共归纳语句:

def InfSeq (R : α α Prop) (a : α) : Prop := b, R a b InfSeq R b coinductive_fixpoint

对应的单调函数为:

def F (R : α α Prop) (a : α) (P : α Prop) : Prop := b, R a b P b

由于 InfSeqF 的最大不动点,因此存在小于 F 中其图像的任何谓词,足以表明满足该谓词的每个元素也满足 InfSeq。 换句话说,为了证明InfSeq R a,只需证明谓词P使得 (a : α), P a F R a P (a : α), P a b, R a b P b,然后显示P a就足够了。

该共归纳原理被命名为 InfSeq.coinduct

InfSeq.coinduct {α} (R : α α Prop) (pred : α Prop) : ( (a : α), pred a b, R a b pred b) (a : α), pred a InfSeq R a
Simple Proof by Coinduction

InfSeq 指出关系中有无限的元素序列,具有给定的起点:

def InfSeq (R : α α Prop) (a : α) : Prop := b, R a b InfSeq R b coinductive_fixpoint

如果 R a a 成立,则存在一个在 a 处循环的平凡无限链:

theorem cycle_InfSeq {R : α α Prop} (a : α) : R a a InfSeq R a := α:Sort u_1R:α α Propa:αR a a InfSeq R a α:Sort u_1R:α α Propa:α (a : α), R a a b, R a b R b b α:Sort u_1R:α α Propa:αx:αh:R x x b, R x b R b b All goals completed! 🐙
Infinite Chains of Less-Than

InfSeq 指出关系中有无限的元素序列,具有给定的起点:

def InfSeq (R : α α Prop) (a : α) : Prop := b, R a b InfSeq R b coinductive_fixpoint

存在与 (· < ·) 相关的自然数的无限链。 所有自然数都启动这样的链,因此谓词可以很简单:

theorem lt_InfSeq {n : Nat} : InfSeq (· < ·) n := n:NatInfSeq (fun x1 x2 => x1 < x2) n n:Nat (a : Nat), True b, a < b Truen:NatTrue n:Nat (a : Nat), True b, a < b True n:Natk:Natx✝:True b, k < b True n:Natk:Natx✝:Truek < k + 1 True All goals completed! 🐙 n:NatTrue All goals completed! 🐙
DFA Language Equivalence

同归纳谓词自然地捕获了类似互模拟的概念。

确定性有限自动机由一组状态 Q、字母表 AQ 中的起始状态 q、定义接受状态的 Q 的子集以及将状态和字母表的元素带到新状态的转换函数给出:

structure DFA (Q : Type) (A : Type) : Type where q₀ : Q δ : Q A Q accepting : Q Bool

同一字母表上的两个自动机在就给定状态对是否接受达成一致时,具有来自给定状态对的等效语言,并且根据其转换函数,它们还具有来自所有后继状态的等效语言:

def languageEquivalent (M : DFA Q A) (M' : DFA Q' A) (q : Q) (q' : Q') : Prop := M.accepting q = M'.accepting q' (a : A), languageEquivalent M M' (M.δ q a) (M'.δ q' a) coinductive_fixpoint

共归纳原理捕捉了确定性自动机互模拟的标准概念:

languageEquivalent.coinduct {Q A Q' : Type} (M : DFA Q A) (M' : DFA Q' A) (pred : Q Q' Prop) : ( (q : Q) (q' : Q'), pred q q' M.accepting q = M'.accepting q' (a : A), pred (M.δ q a) (M'.δ q' a)) (q : Q) (q' : Q'), pred q q' languageEquivalent M M' q q'

可以用来证明这两个 DFA 具有等效的语言:

b a, b a fail ok
a, b b b a a fail ok start

这些 DFA 可以使用以下定义来表示:

inductive Alphabet where | a | b inductive Q1 where | ok | fail def loop : DFA Q1 Alphabet where q₀ := .ok δ | .ok, .a => .ok | _, _ => .fail accepting | .ok => True | _ => False inductive Q2 where | start | ok | fail def cycle : DFA Q2 Alphabet where q₀ := .start δ | .start, .a => .ok | .ok, .a => .start | _, _ => .fail accepting | .start | .ok => True | .fail => False

为了证明它们是等价的,第一步是定义一个捕获它们等价状态的关系。 然后,共归纳证明了它们在语言等价性方面实际上是等价的:

theorem loop_equiv_cycle : languageEquivalent loop cycle loop.q₀ cycle.q₀ := languageEquivalent loop cycle loop.q₀ cycle.q₀ r:Q1 Q2 Prop := fun x x_1 => match x, x_1 with | Q1.ok, Q2.start => True | Q1.ok, Q2.ok => True | Q1.fail, Q2.fail => True | x, x_2 => FalselanguageEquivalent loop cycle loop.q₀ cycle.q₀ r:Q1 Q2 Prop := fun x x_1 => match x, x_1 with | Q1.ok, Q2.start => True | Q1.ok, Q2.ok => True | Q1.fail, Q2.fail => True | x, x_2 => False (q : Q1) (q' : Q2), r q q' loop.accepting q = cycle.accepting q' (a : Alphabet), r (loop.δ q a) (cycle.δ q' a)r:Q1 Q2 Prop := fun x x_1 => match x, x_1 with | Q1.ok, Q2.start => True | Q1.ok, Q2.ok => True | Q1.fail, Q2.fail => True | x, x_2 => Falser loop.q₀ cycle.q₀ r:Q1 Q2 Prop := fun x x_1 => match x, x_1 with | Q1.ok, Q2.start => True | Q1.ok, Q2.ok => True | Q1.fail, Q2.fail => True | x, x_2 => False (q : Q1) (q' : Q2), r q q' loop.accepting q = cycle.accepting q' (a : Alphabet), r (loop.δ q a) (cycle.δ q' a) r:Q1 Q2 Prop := fun x x_1 => match x, x_1 with | Q1.ok, Q2.start => True | Q1.ok, Q2.ok => True | Q1.fail, Q2.fail => True | x, x_2 => False (q : Q1) (q' : Q2), r q q' ((match q with | Q1.ok => decide True | x => decide False) = match q' with | Q2.start => decide True | Q2.ok => decide True | Q2.fail => decide False) (a : Alphabet), r (match q, a with | Q1.ok, Alphabet.a => Q1.ok | x, x_1 => Q1.fail) (match q', a with | Q2.start, Alphabet.a => Q2.ok | Q2.ok, Alphabet.a => Q2.start | x, x_1 => Q2.fail) r:Q1 Q2 Prop := fun x x_1 => match x, x_1 with | Q1.ok, Q2.start => True | Q1.ok, Q2.ok => True | Q1.fail, Q2.fail => True | x, x_2 => False (q : Q1) (q' : Q2), r q q' ((match q with | Q1.ok => decide True | x => decide False) = match q' with | Q2.start => decide True | Q2.ok => decide True | Q2.fail => decide False) (a : Alphabet), r (match q, a with | Q1.ok, Alphabet.a => Q1.ok | x, x_1 => Q1.fail) (match q', a with | Q2.start, Alphabet.a => Q2.ok | Q2.ok, Alphabet.a => Q2.start | x, x_1 => Q2.fail) All goals completed! 🐙 r:Q1 Q2 Prop := fun x x_1 => match x, x_1 with | Q1.ok, Q2.start => True | Q1.ok, Q2.ok => True | Q1.fail, Q2.fail => True | x, x_2 => Falser loop.q₀ cycle.q₀ All goals completed! 🐙

7.6.5.1.2. 感应固定点🔗

Lean.Parser.Command.declaration : commandinductive_fixpoint 子句将谓词定义为其定义方程的最小不动点。 该函数相对于 Lean.Order.ImplicationOrderProp 上的顺序)必须是单调的,其中 P ⊑ Q 表示 P → Q。 这为谓词提供了普通 Lean.Parser.Command.declaration : commandinductive 类型声明的替代方案,并且是 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 的对偶。

在大多数情况下,普通的归纳类型声明更为方便。 但是,与普通归纳类型声明相比,归纳固定点定义有两个关键优势,使它们更适合某些特殊用例:

  • 普通归纳类型声明具有语法正性条件,其中归纳类型的递归出现不能出现在负位置。相反,归纳固定点需要单调性,这是一个语义条件。

  • 归纳固定点可以与共归纳固定点相互定义,从而允许混合归纳-共归纳谓词。

对于每个归纳固定点定义,都会自动证明归纳原理。 该归纳原理与为归纳类型声明生成的相应归纳原理具有相同的逻辑强度,但其表述方式略有不同,必须明确应用。

正如共归纳固定点一样,归纳固定点定义在定义上不会减少。 它们可以使用生成的等式引理展开,并且它们的归纳原理允许它们用于证明。

Reflexive Transitive Closures as Inductive Fixpoints

关系的自反传递闭包可以定义为归纳谓词:

inductive Star (R : α α Prop) : α α Prop where | refl : x : α, Star R x x | step : x y z, R x y Star R y z Star R x z

相同的谓词可以定义为最小不动点。

def StarInd (tr : α α Prop) (q₁ q₂ : α) : Prop := q₁ = q₂ (z : α), (tr q₁ z StarInd tr z q₂) inductive_fixpoint

产生归纳原理:

StarInd.induct (tr : α α Prop) (q₂ : α) (pred : α Prop) (hyp : (q₁ : α), (q₁ = q₂ z, tr q₁ z pred z) pred q₁) (q₁ : α) : StarInd tr q₁ q₂ pred q₁

可以利用归纳原理证明这两个公式是等价的:

theorem star_implies_starInd (R : α α Prop) : a b : α, Star R a b = StarInd R a b := α:Sort u_1R:α α Prop (a b : α), Star R a b = StarInd R a b α:Sort u_1R:α α Propa:αb:αStar R a b = StarInd R a b α:Sort u_1R:α α Propa:αb:αStar R a b StarInd R a b α:Sort u_1R:α α Propa:αb:αStar R a b StarInd R a bα:Sort u_1R:α α Propa:αb:αStarInd R a b Star R a b α:Sort u_1R:α α Propa:αb:αStar R a b StarInd R a b α:Sort u_1R:α α Propa:αb:αh:Star R a bStarInd R a b α:Sort u_1R:α α Propa:αb:αx✝:αStarInd R x✝ x✝α:Sort u_1R:α α Propa:αb:αx✝:αy✝:αz✝:αa✝¹:R x✝ y✝a✝:Star R y✝ z✝a_ih✝:StarInd R y✝ z✝StarInd R x✝ z✝ α:Sort u_1R:α α Propa:αb:αx✝:αStarInd R x✝ x✝α:Sort u_1R:α α Propa:αb:αx✝:αy✝:αz✝:αa✝¹:R x✝ y✝a✝:Star R y✝ z✝a_ih✝:StarInd R y✝ z✝StarInd R x✝ z✝ All goals completed! 🐙 α:Sort u_1R:α α Propa:αb:αStarInd R a b Star R a b α:Sort u_1R:α α Propa:αb:α (q₁ : α), (q₁ = b z, R q₁ z Star R z b) Star R q₁ b All goals completed! 🐙

7.6.5.1.3. 相互块中的混合归纳-共归纳谓词🔗

mutual block 可以混合 Lean.Parser.Command.declaration : commandcoinductive_fixpointLean.Parser.Command.declaration : commandinductive_fixpoint 子句。 块中的每个定义都必须使用这两个子句之一。 该结构使用两个 Prop 上的晶格结构ImplicationOrder 用于归纳定义,ReverseImplicationOrder 用于共归纳定义。 在这两种情况下,都会计算相应晶格的最小不动点;使用反向蕴含顺序,最小不动点与标准顺序中的最大不动点重合。 这是可能的,因为当遇到否定或蕴涵时,单调性引理会在两个顺序之间翻转。

Mixed Inductive-Coinductive Mutual Block

该相互块包含相互递归的共归纳和归纳谓词:

mutual def tick : Prop := ¬tock coinductive_fixpoint def tock : Prop := ¬tick inductive_fixpoint end

为互块中的第一个定义生成互感应原理:

tick.mutual_induct (pred_1 pred_2 : Prop) : (pred_1 pred_2 False) ((pred_1 False) pred_2) (pred_1 tick) (tock pred_2)

7.6.5.2. 更多例子🔗

Infinite Chains from Universal Reachability

关系的自反传递闭包通过归纳指定:

inductive Star (R : α α Prop) : α α Prop where | refl : x : α, Star R x x | step : x y z, R x y Star R y z Star R x z

无限序列通过共归纳法指定:

def InfSeq (R : α α Prop) (a : α) : Prop := b, R a b InfSeq R b coinductive_fixpoint

如果从起始状态 a 通过自反传递闭包可到达的每个状态都有后继,则存在从 a 开始的无限链。 谓词 AllSeqInf 声明每个可达状态都有一个后继:

def AllSeqInf (R : α α Prop) (x : α) : Prop := y : α, Star R x y z, R y z

通过共归纳证明这意味着存在无限链:

theorem infSeq_of_allSeqInf (R : α α Prop) : x, AllSeqInf R x InfSeq R x := α:Sort u_1R:α α Prop (x : α), AllSeqInf R x InfSeq R x α:Sort u_1R:α α Prop (a : α), AllSeqInf R a b, R a b AllSeqInf R b α:Sort u_1R:α α Propx:αH:AllSeqInf R x b, R x b AllSeqInf R b α:Sort u_1R:α α Propx:αH: (y : α), Star R x y z, R y z b, R x b AllSeqInf R b α:Sort u_1R:α α Propx:αH: (y : α), Star R x y z, R y zH': z, R x z b, R x b AllSeqInf R b α:Sort u_1R:α α Propx:αH: (y : α), Star R x y z, R y zy:αRxy:R x y b, R x b AllSeqInf R b All goals completed! 🐙
Coinduction Up-To Transitive Closure

强化的共归纳原理允许共归纳假设应用于传递闭包。 给定一个谓词 X,使得每个 X 状态通过一个或多个 R 步引导至另一个 X 状态,则每个 X 状态满足 InfSeq R

inductive Star (R : α α Prop) : α α Prop where | refl : x : α, Star R x x | step : x y z, R x y Star R y z Star R x z def InfSeq (R : α α Prop) (a : α) : Prop := b, R a b InfSeq R b coinductive_fixpoint variable {α : Sort _} {R : α α Prop} inductive Plus (R : α α Prop) : α α Prop where | left : a b c, R a b Star R b c Plus R a c theorem plusStar (a b : α) : Plus R a b Star R a b := α:Sort u_1R:α α Propa:αb:αPlus R a b Star R a b α:Sort u_1R:α α Propa:αb:αh:Plus R a bStar R a b; α:Sort u_1R:α α Propa:αb:αb✝:αa✝¹:R a b✝a✝:Star R b✝ bStar R a b case left _ h₂ h₃ α:Sort u_1R:α α Propa:αb:αb✝:αh₂:R a b✝h₃:Star R b✝ bStar R a b All goals completed! 🐙 theorem plusStarTrans (a b c : α) : Star R a b Plus R b c Plus R a c := α:Sort u_1R:α α Propa:αb:αc:αStar R a b Plus R b c Plus R a c α:Sort u_1R:α α Propa:αb:αc:αs:Star R a bp:Plus R b cPlus R a c; α:Sort u_1R:α α Propa:αb:αc:αx✝:αp:Plus R x✝ cPlus R x✝ cα:Sort u_1R:α α Propa:αb:αc:αx✝:αy✝:αz✝:αa✝¹:R x✝ y✝a✝:Star R y✝ z✝a_ih✝:Plus R z✝ c Plus R y✝ cp:Plus R z✝ cPlus R x✝ c case refl α:Sort u_1R:α α Propa:αb:αc:αx✝:αp:Plus R x✝ cPlus R x✝ c All goals completed! 🐙 case step d e _ rel _ ih α:Sort u_1R:α α Propa:αb:αc:αd:αe:αz✝:αrel:R x✝ y✝a✝:Star R y✝ z✝ih:Plus R z✝ c Plus R y✝ cp:Plus R z✝ cPlus R x✝ c All goals completed! 🐙 variable (X : α Prop) theorem infSeqCoinductionUpTo : ( (a : α), X a b, Plus R a b X b) (a : α), X a InfSeq R a := α:Sort u_1R:α α PropX:α Prop(∀ (a : α), X a b, Plus R a b X b) (a : α), X a InfSeq R a α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X aInfSeq R a α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X a (a : α), ( b, Star R a b X b) b, R a b b_1, Star R b b_1 X b_1α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X a b, Star R a b X b case x α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X a b, Star R a b X b α:Sort u_1R:α α PropX:α Proph₁✝: (a : α), X a b, Plus R a b X ba:αrel:X aa':αh₁:Plus R a a'h₂:X a' b, Star R a b X b All goals completed! 🐙 case hyp α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X a (a : α), ( b, Star R a b X b) b, R a b b_1, Star R b b_1 X b_1 α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X aa0:αa1:αh₃:Star R a0 a1h₄:X a1 b, R a0 b b_1, Star R b b_1 X b_1 α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X aa0:αa1:αh₃:Star R a0 a1h₄:X a1mid:αh₅:Plus R a1 midh₆:X mid b, R a0 b b_1, Star R b b_1 X b_1 α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X aa0:αa1:αh₃:Star R a0 a1h₄:X a1mid:αh₅:Plus R a1 midh₆:X midt:Plus R a0 mid b, R a0 b b_1, Star R b b_1 X b_1 α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X aa0:αa1:αh₃:Star R a0 a1h₄:X a1mid:αh₅:Plus R a1 midh₆:X midb✝:αa✝¹:R a0 b✝a✝:Star R b✝ mid b, R a0 b b_1, Star R b b_1 X b_1 case left mid2 rel2 s α:Sort u_1R:α α PropX:α Proph₁: (a : α), X a b, Plus R a b X ba:αrel:X aa0:αa1:αh₃:Star R a0 a1h₄:X a1mid:αh₅:Plus R a1 midh₆:X midmid2:αrel2:R a0 b✝s:Star R b✝ mid b, R a0 b b_1, Star R b b_1 X b_1 All goals completed! 🐙

7.6.5.3. coinductive 命令🔗

Lean.Parser.Command.declaration : commandcoinductive 命令提供用于定义 共归纳谓词 的语法,该语法镜像 Lean.Parser.Command.declaration : commandinductive 声明的语法。 该声明不是使用 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 编写递归函数,而是使用构造函数编写,就像归纳类型一样。

syntaxCoinductive Predicates
command ::= ...
    | `declModifiers` is the collection of modifiers on a declaration:
* a doc comment `/-- ... -/`
* a list of attributes `@[attr1, attr2]`
* a visibility specifier, `private` or `public`
* `protected`
* `noncomputable`
* `unsafe`
* `partial` or `nonrec`

All modifiers are optional, and have to come in the listed order.

`nestedDeclModifiers` is the same as `declModifiers`, but attributes are printed
on the same line as the declaration. It is used for declarations nested inside other syntax,
such as inductive constructors, structure projections, and `let rec` / `where` definitions. coinductive `declId` matches `foo` or `foo.{u,v}`: an identifier possibly followed by a list of universe names declId `optDeclSig` matches the signature of a declaration with optional type: a list of binders and then possibly `: type` (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 | bracketedBinder)* : term where
        ctor*

Lean.Parser.Command.declaration : commandcoinductive 命令通过指定其构造函数来定义共归纳谓词。 它只能用于定义谓词,即在 Prop 中赋值的类型。

Lean.Parser.Command.declaration : commandcoinductive 命令定义与相应的 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 定义相同的谓词。 它还生成构造函数和案例分析原理,就像普通的 Lean.Parser.Command.declaration : commandinductive 声明一样。

Coinductive Predicate via coinductive

前面示例中的谓词 InfSeq 可以等效地使用 Lean.Parser.Command.coinductivecoinductive 命令定义:

variable (α : Type) coinductive InfSeq (r : α α Prop) : α Prop where | step : r a b InfSeq r b InfSeq r a

这会生成一个构造函数和一个 代归纳原理

InfSeq.step (α : Type) (r : α α Prop) {a b : α} : r a b InfSeq α r b InfSeq α r aInfSeq.coinduct (α : Type) (r : α α Prop) (pred : α Prop) : ( (a : α), pred a b, r a b pred b) (a : α), pred a InfSeq α r a

还生成了案例分析原理:

InfSeq.casesOn (α : Type) (r : α α Prop) {motive : (a : α) InfSeq α r a Prop} {a : α} (t : InfSeq α r a) : ( {a b} (a_1 : r a b) (a_2 : InfSeq α r b), motive a (InfSeq.step α r a_1 a_2)) motive a t

案例分析可通过 cases策略用于证明:

theorem InfSeq.casesOnTest (r : α α Prop) (a : α) : InfSeq α r a b, r a b := α:Typer:α α Propa:αInfSeq α r a b, r a b α:Typer:α α Propa:αh:InfSeq α r a b, r a b α:Typer:α α Propa:αb✝:αa✝¹:InfSeq α r b✝a✝:r a b✝ b, r a b case step b _ hr α:Typer:α α Propa:αb:αa✝:InfSeq α r b✝hr:r a b✝ b, r a b All goals completed! 🐙

7.6.5.3.1. 精化🔗

在底层,Lean.Parser.Command.declaration : commandcoinductive 命令分几个步骤详细说明。 首先,它被当作普通的 Lean.Parser.Command.declaration : commandinductive 声明来处理。 然而,在使用内核注册类型之前,会创建 flat inductor(也称为 functor):构造函数前提中共归纳谓词的每个递归出现都被显式参数替换。

Flat Inductive

此示例使用无限序列的共归纳规范:

coinductive InfSeq (r : α α Prop) : α Prop where | step : r a b InfSeq r b InfSeq r a

对于 InfSeq,生成的平坦电感为:

InfSeq._functor : (α : Type) (α α Prop) (α Prop) α Prop

它的构造函数使用谓词参数来代替递归调用:

set_option pp.proofs true in inductive InfSeq._functor : (α : Type) (α α Prop) (α Prop) α Prop number of parameters: 3 constructors: InfSeq._functor.step : (α : Type) (r : α α Prop) (InfSeq._functor.call : α Prop) {a b : α}, r a b InfSeq._functor.call b InfSeq._functor α r InfSeq._functor.call a#print InfSeq._functor
inductive InfSeq._functor : (α : Type)  (α  α  Prop)  (α  Prop)  α  Prop
number of parameters: 3
constructors:
InfSeq._functor.step :  (α : Type) (r : α  α  Prop) (InfSeq._functor.call : α  Prop) {a b : α},
  r a b  InfSeq._functor.call b  InfSeq._functor α r InfSeq._functor.call a

然后构造等效的 存在形式,将每个构造函数表示为从属乘积(即存在量词和连词)的析取。 这种形式用于单调性检查和生成可读的共归纳原理。

Existential Form
coinductive InfSeq (r : α α Prop) : α Prop where | step : r a b InfSeq r b InfSeq r a set_option pp.proofs true in def InfSeq._functor.existential : (α : Type) (α α Prop) (α Prop) α Prop := fun α r InfSeq._functor.call a => b, r a b InfSeq._functor.call b#print InfSeq._functor.existential
def InfSeq._functor.existential : (α : Type)  (α  α  Prop)  (α  Prop)  α  Prop :=
fun α r InfSeq._functor.call a =>  b, r a b  InfSeq._functor.call b

这两种形式通过等价定理联系起来:

InfSeq._functor.existential_equiv : (α : Type) (r : α α Prop) (InfSeq._functor.call : α Prop) (a : α), InfSeq._functor α r InfSeq._functor.call a b, r a b InfSeq._functor.call b#check @InfSeq._functor.existential_equiv
InfSeq._functor.existential_equiv :  (α : Type) (r : α  α  Prop) (InfSeq._functor.call : α  Prop) (a : α),
  InfSeq._functor α r InfSeq._functor.call a   b, r a b  InfSeq._functor.call b

然后使用 部分固定点 机制和 Lean.Order.ReverseImplicationOrder 完整格实例将存在形式注册为共归纳谓词。 利用平面归纳和存在形式之间的对应关系,生成构造函数和案例分析消除器,就像常规的归纳类型一样。

为名为 P 的共归纳谓词生成以下声明:

  • P._functor扁平电感

  • P._functor.existential存在形式

  • P._functor.existential_equiv:两种形式之间的等效性

  • P.functor_unfold:将共归纳谓词连接到其平面归纳的定理

  • 构造函数(例如,P.step):对应于声明中的每个构造函数

  • P.casesOn:案例分析原理

7.6.5.3.2. 互感和感性块🔗

在包含 Lean.Parser.Command.coinductivecoinductive 定义的 相互块 中,Lean.Parser.Command.inductiveIn Lean, every concrete type other than the universes and every type constructor other than dependent arrows is an instance of a general family of type constructions known as inductive types. It is remarkable that it is possible to construct a substantial edifice of mathematics based on nothing more than the type universes, dependent arrow types, and inductive types; everything else follows from those. Intuitively, an inductive type is built up from a specified list of constructors. For example, `List α` is the list of elements of type `α`, and is defined as follows: ``` inductive List (α : Type u) where | nil | cons (head : α) (tail : List α) ``` A list of elements of type `α` is either the empty list, `nil`, or an element `head : α` followed by a list `tail : List α`. See [Inductive types](https://lean-lang.org/theorem_proving_in_lean4/inductive_types.html) for more information. inductive 关键字被重新解释:它不是注册为普通的内核归纳类型,而是通过晶格理论 感应固定点 机制进行详细说明。 这允许在同一个共同块中混合共归纳谓词和归纳谓词。

Mutual Coinductive-Inductive Block

谓词 TickTock 相互定义,其中 Tick 作为共归纳谓词,Tock 作为归纳谓词:

mutual coinductive Tick : Prop where | mk : ¬Tock Tick inductive Tock : Prop where | mk : ¬Tick Tock end

两个构造函数都可用:

Tick.mk : ¬Tock Tick#check @Tick.mk
Tick.mk : ¬Tock  Tick
Tock.mk : ¬Tick Tock#check @Tock.mk
Tock.mk : ¬Tick  Tock

产生互感原理:

Tick.mutual_induct : (pred_1 pred_2 : Prop), (pred_1 pred_2 False) ((pred_1 False) pred_2) (pred_1 Tick) (Tock pred_2)#check @Tick.mutual_induct
Tick.mutual_induct :  (pred_1 pred_2 : Prop),
  (pred_1  pred_2  False)  ((pred_1  False)  pred_2)  (pred_1  Tick)  (Tock  pred_2)

7.6.5.3.3. 限制🔗

Lean.Parser.Command.declaration : commandcoinductive 命令具有以下限制:

  • 它只能定义谓词,即 Prop 中赋值的类型。 尝试在 Type 或更高的宇宙中定义共归纳类型会导致错误。

  • 定义的谓词可能没有 宏范围

  • 尚不支持通过 Lean.Parser.Term.match : termPattern matching. `match e, ... with | p, ... => f | ...` matches each given term `e` against each pattern `p` of a match alternative. When all patterns of an alternative match, the `match` term evaluates to the value of the corresponding right-hand side `f` with the pattern variables bound to the respective matched values. If used as `match h : e, ... with | p, ... => f | ...`, `h : e = p` is available within `f`. When not constructing a proof, `match` does not automatically substitute variables matched on in dependent variables' types. Use `match (generalizing := true) ...` to enforce this. Syntax quotations can also be used in a pattern match. This matches a `Syntax` value against quotations, pattern variables, or `_`. Quoted identifiers only match identical identifiers - custom matching such as by the preresolved names only should be done explicitly. `Syntax.atom`s are ignored during matching by default except when part of a built-in literal. For users introducing new atoms, we recommend wrapping them in dedicated syntax kinds if they should participate in matching. For example, in ```lean syntax "c" ("foo" <|> "bar") ... ``` `foo` and `bar` are indistinguishable during matching, but in ```lean syntax foo := "foo" syntax "c" (foo <|> "bar") ... ``` they are not. match 的模式匹配;请改用 cases策略。

Restriction to Predicates

尝试定义不是谓词的共归纳类型会导致错误:

coinductive `coinductive` keyword can only be used to define predicatesMyNat where | zero : MyNat | succ : MyNat MyNat
`coinductive` keyword can only be used to define predicates

7.6.5.4. 理论与构建🔗

共归纳和归纳谓词的构造建立在完全格的 Knaster-Tarski 不动点定理的基础上。 虽然 部分固定点递归 依赖于链完整偏序 (Lean.Order.CCPO),但共归纳和归纳谓词使用更强的 完整格 概念。

关键思想是 Prop 带有按蕴涵排序的 完整格 结构(P ⊑ QP → Q 时),并且完整格上的任何单调内函数都具有根据 Knaster-Tarski 定理的最小和最大不动点。 共导谓词使用 反向蕴涵顺序(当 Q → P 时为 P ⊑ Q),因此此反向顺序中的最小固定点是标准顺序中的最大固定点。 对于 α → Prop 形式的谓词,此点阵结构到函数类型的逐点提升提供了必要的设置。 对于互块,完全格的乘积又是完全格。 该结构与 部分固定点 机械共享其内部结构。

7.6.5.4.1. 完全格子🔗

完整格 是一个偏序,其中每个子集都有一个最小上界,而不仅仅是每个链。

🔗type class
Lean.Order.CompleteLattice.{u} (α : Sort u) : Sort (max 1 u)
Lean.Order.CompleteLattice.{u} (α : Sort u) : Sort (max 1 u)

A complete lattice is a partial order where every subset has a least upper bound.

Instance Constructor

Lean.Order.CompleteLattice.mk.{u}

Extends

Methods

rel : α  α  Prop
Inherited from
  1. PartialOrder α
rel_refl :  {x : α}, x  x
Inherited from
  1. PartialOrder α
rel_trans :  {x y z : α}, x  y  y  z  x  z
Inherited from
  1. PartialOrder α
rel_antisymm :  {x y : α}, x  y  y  x  x = y
Inherited from
  1. PartialOrder α
has_sup :  (c : α  Prop), Exists (is_sup c)

The least upper bound of an arbitrary subset exists.

每个完整的格子都会产生一个 CCPO,因为每个链都是特定的子集,但反之通常不成立。 例如,居住类型上的平面顺序(由 部分固定点 用于尾递归函数)是 CCPO,但不是完整的格。

在完全格中,单调函数的最小不动点可以直接构造为所有预不动点的下确点,遵循 Knaster-Tarski 定理:

🔗def
Lean.Order.lfp.{u} {α : Sort u} [CompleteLattice α] (f : α α) : α
Lean.Order.lfp.{u} {α : Sort u} [CompleteLattice α] (f : α α) : α
🔗theorem
Lean.Order.lfp_fix.{u} {α : Sort u} [CompleteLattice α] {f : α α} (hm : monotone f) : lfp f = f (lfp f)
Lean.Order.lfp_fix.{u} {α : Sort u} [CompleteLattice α] {f : α α} (hm : monotone f) : lfp f = f (lfp f)

相应的归纳原理是 Park 归纳:为了证明某个属性对于最小固定点的所有元素都成立,只需证明该属性通过定义函数的一次应用而得以保留。

🔗theorem
Lean.Order.lfp_le_of_le_monotone.{u} {α : Sort u} [CompleteLattice α] (f : α α) {hm : monotone f} (x : α) : f x x lfp_monotone f hm x
Lean.Order.lfp_le_of_le_monotone.{u} {α : Sort u} [CompleteLattice α] (f : α α) {hm : monotone f} (x : α) : f x x lfp_monotone f hm x

Park induction for least fixpoint of a monotone function f. Takes an explicit witness of f being monotone.

7.6.5.4.2. 命题的格结构🔗

Prop 类型允许两个自然完整的晶格结构,每个结构都会产生一种不同类型的固定点:

  • Lean.Order.ImplicationOrder 通过暗示对命题进行排序:P ⊑ Q 表示 P → Q。 此顺序中的最小固定点产生在定义规则下闭合的最小谓词,对应于 inducing predicate。 这是 Lean.Parser.Command.declaration : commandinductive_fixpoint 使用的顺序。

  • Lean.Order.ReverseImplicationOrder 通过反向蕴涵对命题进行排序:P ⊑ Q 表示 Q → P。 此反转顺序中的最小固定点是标准顺序中的最大固定点,产生与定义规则一致的最大谓词。 这对应于 coininduced predicate。 这是 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 使用的顺序。

完整格子中的箭头类型继承了完整格子结构,完整格子的乘积也是完整格子。 这些闭包属性允许将构造扩展到任意数量的谓词和相互块。

7.6.5.4.3. 单调性🔗

将谓词定义为不动点要求定义方程相对于适当的阶数是单调的。 对于 Lean.Parser.Command.declaration : commandcoinductive 命令以及 Lean.Parser.Command.declaration : commandcoinductive_fixpointLean.Parser.Command.declaration : commandinductive_fixpoint 终止子句,单调性要求是语义而不是语法。 monotonicity策略通过组合用 partial_fixpoint_monotone 属性注册的引理来证明单调性。 这种方法比严格的积极性更为宽容。 例如,通过翻转 Lean.Order.ImplicationOrderLean.Order.ReverseImplicationOrder 之间的顺序可以正确处理否定和蕴涵。 这就是允许在同一个 互块中混合感应和共感应固定点的原因。

monotonicity策略处理的构造集是可扩展的:注册额外的 partial_fixpoint_monotone 引理可教导策略处理新的逻辑连接词或高阶函数。 或者,当通过 monotonicity 子句使用 Lean.Parser.Command.declaration : commandcoinductive_fixpoint 时,可以提供显式单调性证明项。

有关已注册单调性引理的完整列表以及有关单调性策略的更多详细信息,请参阅 部分不动点的理论部分

7.6.6. 不完整和不安全的定义🔗

While most Lean functions can be reasoned about in Lean's 类型论 as well as compiled and run, definitions marked partial or unsafe cannot be meaningfully reasoned about. From the perspective of the logic, partial functions are opaque constants, and theorems that refer to unsafe definitions are summarily rejected. 作为无法使用这些函数进行推理的代价,对它们的要求要少得多; this can make it possible to write programs that would be impractical or cost-prohibitive to prove anything about, while not giving up formal reasoning for the rest. In essence, the partial subset of Lean is a traditional functional programming language that is nonetheless deeply integrated with the theorem proving features, and the unsafe subset features the ability to break Lean's runtime invariants in certain rare situations, at the cost of less integration with Lean's theorem-proving特点。 类似地,noncomputable 定义可能使用在程序中没有意义但在逻辑中有意义的功能。

7.6.6.1. 部分功能🔗

partial 修饰符只能应用于函数定义。 不需要部分函数来证明终止,并且 Lean 不会尝试这样做。 这些函数是“部分”的,因为它们不一定指定从域的每个元素到共域元素的映射,因为它们可能无法终止域的某些或所有元素。 它们被详细精化为包含显式递归的 预定义,并使用内核进行类型检查;然而,它们随后被逻辑视为不透明常量。

函数的返回类型必须是可居住的;这确保了稳健性。 否则,部分函数可能具有 Unit Empty 等类型。 与Empty.elim一起,这样的函数的存在可以用来证明False,即使它不减少。

对于部分定义,内核负责以下内容:

  • 它确保预定义的类型确实是格式良好的类型。

  • 它检查预定义的类型是否为函数类型。

  • 它确保函数的共域被要求的 NonemptyInhabited 实例占据。

  • 如果 Lean 具有递归定义,它会检查生成的术语类型是否正确。

即使递归定义不是内核的 类型论 的一部分,内核仍可用于检查定义主体是否具有正确的类型。 这与其他函数式语言的工作方式相同:通过在定义已与其类型关联的环境中检查主体来对递归的使用进行类型检查。 确保进行类型检查后,主体将被丢弃,内核仅保留不透明常量。 与所有 Lean 函数一样,编译器从详细的 预定义 生成代码。

即使内核未展开部分函数,​​仍然可以推理调用它们的其他函数,只要该推理不依赖于部分函数本身的实现即可。

Partial Functions in Proofs

递归函数 nextPrime 通过反复测试候选数来低效地计算给定数之后的下一个素数。 因为素数有无穷多个,所以它总是终止;然而,提出这个证明并非易事。 因此它被标记为 partial

def isPrime (n : Nat) : Bool := Id.run do for i in [2:n] do if i * i > n then return true if n % i = 0 then return false return true partial def nextPrime (n : Nat) : Nat := let n := n + 1 if isPrime n then n else nextPrime n

尽管如此,还是可以证明以下两个函数是相等的:

def answerUser (n : Nat) : String := s!"The next prime is {nextPrime n}" def answerOtherUser (n : Nat) : String := " ".intercalate [ "The", "next", "prime", "is", toString (nextPrime n) ]

事实上,证明是由 rfl 提供的:

theorem answer_eq_other : answerUser = answerOtherUser := answerUser = answerOtherUser All goals completed! 🐙

7.6.6.2. 不安全的定义🔗

不安全定义的保障措施比部分函数还要少。 它们的共域不需要被占用,它们不限于函数定义,并且它们可以访问 Lean 的特征,这些特征可能违反内部不变量或破坏抽象。 因此,它们根本不能用作数学推理的一部分。

虽然部分函数被 类型论 视为不透明常量,但不安全定义只能从其他不安全定义引用。 因此,任何调用不安全函数的函数本身必定是不安全的。 不允许将定理宣布为不安全。

除了不受限制地使用递归之外,不安全函数还可以从一种类型转换为另一种类型、检查两个值是否是内存中的同一对象、检索指针值以及从其他纯代码运行 IO 操作。 使用这些运算符需要彻底了解 Lean 实现。

🔗unsafe def
unsafeCast.{u, v} {α : Sort u} {β : Sort v} (a : α) : β
unsafeCast.{u, v} {α : Sort u} {β : Sort v} (a : α) : β

This function will cast a value of type α to type β, and is a no-op in the compiler. This function is extremely dangerous because there is no guarantee that types α and β have the same data representation, and this can lead to memory unsafety. It is also logically unsound, since you could just cast True to False. For all those reasons this function is marked as unsafe.

It is implemented by lifting both α and β into a common universe, and then using cast (lcProof : ULift (PLift α) = ULift (PLift β)) to actually perform the cast. All these operations are no-ops in the compiler.

Using this function correctly requires some knowledge of the data representation of the source and target types. Some general classes of casts which are safe in the current runtime:

  • Array α to Array β where α and β have compatible representations, or more generally for other inductive types.

  • Quot α r and α.

  • @Subtype α p and α, or generally any structure containing only one non-Prop field of type α.

  • Casting α to/from NonScalar when α is a boxed generic type (i.e. a function that accepts an arbitrary type α and is not specialized to a scalar type like UInt8).

🔗unsafe def
ptrEq.{u_1} {α : Type u_1} (a b : α) : Bool
ptrEq.{u_1} {α : Type u_1} (a b : α) : Bool

Compares two objects for pointer equality.

Two objects are pointer-equal if, at runtime, they are allocated at exactly the same address. This function is unsafe because it can distinguish between definitionally equal values.

🔗unsafe def
ptrEqList.{u_1} {α : Type u_1} (as bs : List α) : Bool
ptrEqList.{u_1} {α : Type u_1} (as bs : List α) : Bool

Compares two lists of objects for element-wise pointer equality. Returns true if both lists are the same length and the objects at the corresponding indices of each list are pointer-equal.

Two objects are pointer-equal if, at runtime, they are allocated at exactly the same address. This function is unsafe because it can distinguish between definitionally equal values.

🔗unsafe opaque
ptrAddrUnsafe.{u} {α : Type u} (a : α) : USize
ptrAddrUnsafe.{u} {α : Type u} (a : α) : USize

Returns the address at which an object is allocated.

This function is unsafe because it can distinguish between definitionally equal values.

🔗unsafe opaque
isExclusiveUnsafe.{u} {α : Type u} (a : α) : Bool
isExclusiveUnsafe.{u} {α : Type u} (a : α) : Bool

Returns true if a is an exclusive object.

An object is exclusive if it is single-threaded and its reference counter is 1. This function is unsafe because it can distinguish between definitionally equal values.

🔗unsafe def
unsafeIO {α : Type} (fn : IO α) : Except IO.Error α
unsafeIO {α : Type} (fn : IO α) : Except IO.Error α

Executes arbitrary side effects in a pure context, with exceptions indicated via Except. This a dangerous operation that can easily undermine important assumptions about the meaning of Lean programs, and it should only be used with great care and a thorough understanding of compiler internals, and even then only to implement observationally pure operations.

This function is not a good way to convert an EIO α or IO α into an α. Instead, use do-notation.

Because the resulting value is treated as a side-effect-free term, the compiler may re-order, duplicate, or delete calls to this function. The side effect may even be hoisted into a constant, causing the side effect to occur at initialization time, even if it would otherwise never be called.

🔗unsafe def
unsafeEIO {ε α : Type} (fn : EIO ε α) : Except ε α
unsafeEIO {ε α : Type} (fn : EIO ε α) : Except ε α

Executes arbitrary side effects in a pure context, with exceptions indicated via Except. This a dangerous operation that can easily undermine important assumptions about the meaning of Lean programs, and it should only be used with great care and a thorough understanding of compiler internals, and even then only to implement observationally pure operations.

This function is not a good way to convert an EIO α or IO α into an α. Instead, use do-notation.

Because the resulting value is treated as a side-effect-free term, the compiler may re-order, duplicate, or delete calls to this function. The side effect may even be hoisted into a constant, causing the side effect to occur at initialization time, even if it would otherwise never be called.

🔗unsafe def
unsafeBaseIO {α : Type} (fn : BaseIO α) : α
unsafeBaseIO {α : Type} (fn : BaseIO α) : α

Executes arbitrary side effects in a pure context. This a dangerous operation that can easily undermine important assumptions about the meaning of Lean programs, and it should only be used with great care and a thorough understanding of compiler internals, and even then only to implement observationally pure operations.

This function is not a good way to convert a BaseIO α into an α. Instead, use do-notation.

Because the resulting value is treated as a side-effect-free term, the compiler may re-order, duplicate, or delete calls to this function. The side effect may even be hoisted into a constant, causing the side effect to occur at initialization time, even if it would otherwise never be called.

通常,不安全运算符用于编写利用低级细节的快速代码。 正如 Lean 代码可以在运行时通过 FFI 替换为 C 代码一样, 安全 Lean 代码可以替换为运行时程序的不安全 Lean 代码。 这是通过将 implemented_by 属性添加到要替换的函数(通常是 opaque 定义)来完成的。 虽然这不会威胁到 Lean 作为逻辑的健全性,因为要替换的常量已经由内核检查过,并且不安全的替换仅在运行时代码中使用,但它仍然存在风险。 C 代码和不安全代码都可能执行任意副作用。

attributeReplacing Run-Time Implementations

implemented_by 属性指示编译器在编译代码中将一个常量替换为另一个常量。 替换常数可能不安全。

attr ::= ...
    | implemented_by ident
Checking Equality with Pointers

通常,BEq 实例的相等谓词必须完全遍历其两个参数以确定它们是否相等。 如果它们实际上是内存中的同一个对象,那么这确实是浪费。 可以在遍历之前使用指针相等测试来捕获这种情况。

所比较的类型是 Tree,一种二叉树类型。

inductive Tree α where | empty | branch (left : Tree α) (val : α) (right : Tree α)

不安全函数可以使用指针相等来更快地终止结构相等测试,当指针相等失败时回退到结构检查。

unsafe def Tree.fastBEq [BEq α] (t1 t2 : Tree α) : Bool := if ptrEq t1 t2 then true else match t1, t2 with | .empty, .empty => true | .branch l1 x r1, .branch l2 y r2 => if ptrEq x y || x == y then l1.fastBEq l2 && r1.fastBEq r2 else false | _, _ => false

不透明定义上的 implemented_by 属性连接了安全和不安全代码的世界。

@[implemented_by Tree.fastBEq] opaque Tree.beq [BEq α] (t1 t2 : Tree α) : Bool instance [BEq α] : BEq (Tree α) where beq := Tree.beq
Taking Advantage of Run-Time Representations

由于 Fin 与其基础 Nat 的表示方式相同,因此可以用 unsafeCast 替换 List.map Fin.val,以避免实际上不执行任何操作的线性时间遍历:

unsafe def unFinImpl (xs : List (Fin n)) : List Nat := unsafeCast xs @[implemented_by unFinImpl] def unFin (xs : List (Fin n)) : List Nat := xs.map Fin.val

从 Lean内核的角度来看,unFin 是使用 List.map 定义的:

theorem unFin_length_eq_length {xs : List (Fin n)} : (unFin xs).length = xs.length := n:Natxs:List (Fin n)(unFin xs).length = xs.length All goals completed! 🐙

在编译后的代码中,没有对列表的遍历。

这种替换是有风险的:证明和编译代码之间的对应关系完全取决于两个实现的等价性,而这在 Lean 中无法证明。 该对应关系依赖于 Lean 的实现细节。 这些“逃生舱口”应该非常小心地使用。

7.6.7. 控制减少🔗

在检查校样和程序时,Lean 会考虑 reducibility(也称为transparency)。 定义的可归约性控制在精化和证明执行期间展开它的上下文。

可还原性有四个级别:

不可约

不可约定义在精化期间根本没有展开。 通过应用 irreducible 属性可以使定义变得不可约。

半还原

半简化定义不会通过潜在昂贵的自动化(例如类型类实例合成或 simp)展开,但它们会在检查 定义等价 和解析 广义字段表示法 时展开。 Lean.Parser.Command.declaration : commanddef 命令通常创建半可简化定义,除非使用属性指定了不同的可简化级别;但是,默认情况下,使用 良基递归 的定义是不可约的。

隐式可约

隐式可约定义在类型类 实例综​​合 期间以及检查函数隐式参数的 定义等价 期间展开。 这包括普通 隐式 参数、实例隐式 参数和 严格隐式 参数。 所有类型类实例都应该是实例可约简的或可约简的,就像出现在隐式参数类型中并且旨在约简的定义一样。

可还原

可简化的定义基本上随需随处展开。 Type 类实例综合、定义等价 检查以及语言的其余部分将定义本质上视为缩写。 这是 Lean.Parser.Command.declaration : commandabbrev 命令应用的设置。

Reducibility and Instance Synthesis

String 的这三个别名分别是可约、半约和不可约。

abbrev Phrase := String def Clause := String @[irreducible] def Utterance := String

可约化和半约化别名在精化器的 定义等价 检查期间展开,导致它们被视为等同于 String

def hello : Phrase := "Hello" def goodMorning : Clause := "Good morning"

另一方面,不可约别名被拒绝作为字符串类型,因为精化器的 定义等价 测试不会展开它:

def goodEvening : Utterance := Type mismatch "Good evening" has type String but is expected to have type Utterance"Good evening"
Type mismatch
  "Good evening"
has type
  String
but is expected to have type
  Utterance

由于 Phrase 是可约化的,因此 ToString String 实例可以用作 ToString Phrase 实例:

instToStringString#synth ToString Phrase

但是,Clause 是半可约的,因此不能使用 ToString String 实例:

failed to synthesize ToString Clause Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command.#synth ToString Clause
failed to synthesize
  ToString Clause

Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command.

可以通过创建减少为 ToString String 实例的 ToString Clause 实例来显式启用该实例。 此示例之所以有效,是因为在检查 定义等价 时展开了半简化定义:

instance : ToString Clause := inferInstanceAs (ToString String)
Reducibility and Generalized Field Notation

通用字段表示法 在搜索匹配名称时展开可简化和半可简化声明。 给定 List 的半简化别名 Sequence

def Sequence := List def Sequence.ofList (xs : List α) : Sequence α := xs

通用字段表示法允许从 Sequence Nat 类型的术语访问 List.reverse

let xs := Sequence.ofList [1, 2, 3]; List.reverse xs : List Nat#check let xs : Sequence Nat := .ofList [1,2,3]; xs.reverse

然而,声明 Sequence 不可约会阻止展开:

attribute [irreducible] Sequence let xs := Sequence.ofList [1, 2, 3]; sorry : ?m.13#check let xs : Sequence Nat := .ofList [1,2,3]; xs.Invalid field `reverse`: The environment does not contain `Sequence.reverse`, so it is not possible to project the field `reverse` from an expression xs of type `Sequence Nat`reverse
Invalid field `reverse`: The environment does not contain `Sequence.reverse`, so it is not possible to project the field `reverse` from an expression
  xs
of type `Sequence Nat`
attributeReducibility Annotations

可以使用四个可归约属性之一来设置定义的可归约性:

attr ::= ...
    | reducible
attr ::= ...
    | implicit_reducible
attr ::= ...
    | semireducible
attr ::= ...
    | irreducible

这些属性只能在与正在修改的定义相同的文件中全局应用,但它们可以 Lean.Parser.Term.attrKindlocally 应用到任何地方。

7.6.7.1. 还原性和策略🔗

策略with_reduciblewith_reducible_and_instanceswith_unfolding_all 控制大多数策略展开的定义。

Reducibility and Tactics

函数 plussumtally 都是 Nat.add 的同义词,分别是可约、半约和不可约:

abbrev plus := Nat.add def sum := Nat.add @[irreducible] def tally := Nat.add

可约同义词由 simp 展开:

theorem plus_eq_add : plus x y = x + y := x:Naty:Natplus x y = x + y All goals completed! 🐙

然而,半约同义词并未由 simp 展开:

theorem sum_eq_add : sum x y = x + y := x:Naty:Natsum x y = x + y `simp` made no progressx:Naty:Natsum x y = x + y

尽管如此,由 rfl 引发的 定义等价 检查会展开 sum

theorem sum_eq_add : sum x y = x + y := x:Naty:Natsum x y = x + y All goals completed! 🐙

然而,不可约的 tally 不会被 定义等价 约简。

theorem tally_eq_add : tally x y = x + y := x:Naty:Nattally x y = x + y Tactic `rfl` failed: The left-hand side tally x y is not definitionally equal to the right-hand side x + y x y:Nattally x y = x + yx:Naty:Nattally x y = x + y

当显式提供时,simp策略可以展开任何定义,甚至是不可约的定义:

theorem tally_eq_add : tally x y = x + y := x:Naty:Nattally x y = x + y All goals completed! 🐙

类似地,可以通过将证明的一部分放在 with_unfolding_all 块中来指示忽略不可约性:

theorem tally_eq_add : tally x y = x + y := x:Naty:Nattally x y = x + y with_unfolding_all All goals completed! 🐙
Reducibility and Implicit Arguments

函数 plussumtallyNat.add 的同义词,分别是可约化、隐式可约化和不可约化:

abbrev plus := Nat.add @[implicit_reducible] def sum := Nat.add def tally := Nat.add

Nonzero 的实例包含给定数字不等于零的证明。 函数 notZero 从合成实例中提取此证明:

class Nonzero (n : Nat) where non_zero : n 0 instance Nonzero.instSucc : Nonzero (n + 1) where non_zero := n:Natn + 1 0 All goals completed! 🐙 def notZero (n : Nat) [Nonzero n] : n 0 := Nonzero.non_zero

找到可简化定义 plus 的实例:

notZero (plus 2 2) : plus 2 2 0#check notZero (plus 2 2)

还可以找到隐式可约定义 sum。 这是因为类型 Nonzero (sum 2 2)notZero实例隐式 参数的类型。 特别是,sum 被简化为 Nat.add,它本身是隐式可约的,因此类型被简化为 Nonzero 4

notZero (sum 2 2) : sum 2 2 0#check notZero (sum 2 2)

tally 的实例合成失败,因为它没有减少:

#check failed to synthesize instance of type class Nonzero (tally 2 2) Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.notZero (tally 2 2)
failed to synthesize instance of type class
  Nonzero (tally 2 2)

Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.

在其他上下文中,例如调用 simpplus 会展开:

theorem plus_eq_add : plus x y = x + y := x:Naty:Natplus x y = x + y All goals completed! 🐙

然而,隐式可约同义词并未由 simp 展开:

theorem sum_eq_add : sum x y = x + y := x:Naty:Natsum x y = x + y `simp` made no progressx:Naty:Natsum x y = x + y
`simp` made no progress

7.6.7.2. 修改还原性🔗

通过使用 Lean.Parser.Command.attribute : commandattribute 命令应用适当的属性,可以在定义定义的模块中全局修改定义的可约性。 在其他模块中,可以通过使用 local 修饰符应用属性来修改导入定义的可简化性。 Lean.Parser.commandSeal__ : commandThe `seal foo` command ensures that the definition of `foo` is sealed, meaning it is marked as `[irreducible]`. This command is particularly useful in contexts where you want to prevent the reduction of `foo` in proofs. In terms of functionality, `seal foo` is equivalent to `attribute [local irreducible] foo`. This attribute specifies that `foo` should be treated as irreducible only within the local scope, which helps in maintaining the desired abstraction level without affecting global settings. sealLean.Parser.commandUnseal__ : commandThe `unseal foo` command ensures that the definition of `foo` is unsealed, meaning it is marked as `[semireducible]`, the default reducibility setting. This command is useful when you need to allow some level of reduction of `foo` in proofs. Functionally, `unseal foo` is equivalent to `attribute [local semireducible] foo`. Applying this attribute makes `foo` semireducible only within the local scope. unseal 命令是此过程的简写。

syntaxLocal Irreducibility

The seal foo command ensures that the definition of foo is sealed, meaning it is marked as [irreducible]. This command is particularly useful in contexts where you want to prevent the reduction of foo in proofs.

In terms of functionality, seal foo is equivalent to attribute [local irreducible] foo. This attribute specifies that foo should be treated as irreducible only within the local scope, which helps in maintaining the desired abstraction level without affecting global settings.

command ::= ...
    | The `seal foo` command ensures that the definition of `foo` is sealed, meaning it is marked as `[irreducible]`.
This command is particularly useful in contexts where you want to prevent the reduction of `foo` in proofs.

In terms of functionality, `seal foo` is equivalent to `attribute [local irreducible] foo`.
This attribute specifies that `foo` should be treated as irreducible only within the local scope,
which helps in maintaining the desired abstraction level without affecting global settings.
seal ident ident*
syntaxLocal Reducibility

The unseal foo command ensures that the definition of foo is unsealed, meaning it is marked as [semireducible], the default reducibility setting. This command is useful when you need to allow some level of reduction of foo in proofs.

Functionally, unseal foo is equivalent to attribute [local semireducible] foo. Applying this attribute makes foo semireducible only within the local scope.

command ::= ...
    | The `unseal foo` command ensures that the definition of `foo` is unsealed, meaning it is marked as `[semireducible]`, the
default reducibility setting. This command is useful when you need to allow some level of reduction of `foo` in proofs.

Functionally, `unseal foo` is equivalent to `attribute [local semireducible] foo`.
Applying this attribute makes `foo` semireducible only within the local scope.
unseal ident ident*

7.6.7.3. 选项🔗

为了提高性能,精化器和许多策略构建了索引和缓存。 其中许多都考虑了可还原性,如果可还原性发生全局变化,则无法使它们失效并重新生成。 默认情况下,不允许对还原性设置进行不安全的更改,这可能会产生不可预测的结果,但可以通过使用 allowUnsafeReducibility 选项来启用它们。

🔗option
allowUnsafeReducibility

Default value: false

enables users to modify the reducibility settings for declarations even when such changes are deemed potentially hazardous. For example, simp and type class resolution maintain term indices where reducible declarations are expanded.