Lean 语言参考

8. 公理🔗

Axioms 是假设常数。 虽然公理的类型本身必须是类型(即,它必须具有类型 Sort u),但没有其他要求。 公理不会 简化为其他项。

在投入时间构建模型或证明定理之前,可以使用公理来试验某个想法的结果。 它们还可以用于采用 Lean 的 类型论 中无法访问的推理原理; Lean 本身提供了已知是一致的 三个这样的公理。 然而,应该谨慎使用公理:彼此不一致或完全错误的公理会破坏证明的基础。 Lean 自动跟踪每个证明所依赖的公理,以便对其进行审核。

8.1. 公理声明🔗

Axioms 声明包括名称和类型:

syntaxAxiom Declarations
axiom ::= ...
    | axiom `declId` matches `foo` or `foo.{u,v}`: an identifier possibly followed by a list of universe names declId `declSig` matches the signature of a declaration with required type: a list of binders and then `: type` declSig

Axioms 声明可以使用所有可能的 声明修饰符 进行修改。 文档注释、属性、privateprotected 与其他声明具有相同的含义。 修饰符 partialnonrecnoncomputableunsafe 无效。

8.2. 一致性🔗

使用公理是有风险的。 因为它们引入了任何类型的新常量,并且命题类型的居民算作命题的证明,所以公理甚至可以用来证明假命题。 任何依赖于公理的证明只有在该公理既真实又与所使用的其他公理一致的情况下才可以被信任。 就其本质而言,Lean 无法检查新公理是否一致;添加公理时请小心。

Inconsistencies From Axioms

公理可能单独或与其他公理结合引入不一致。

假设一个错误的陈述允许任何陈述被证明:

axiom false_is_true : False theorem two_eq_five : 2 = 5 := false_is_true.elim

与 Lean 的其他属性不兼容的公理也可能引起不一致。 例如,参数性在支持它的语言中使用时是一种强大的推理技术,但它与 Lean 的标准公理不兼容。 如果参数性成立,那么 Wadler 的 Theorems for Free (1989) 介绍中的“自由定理”将是正确的,该定理描述了一种使用参数性推导有关多态函数的定理的技术。 作为一条公理,它写道:

axiom List.free_theorem {α β} (f : {α : _} List α List α) (g : α β) : f (List.map g) = (List.map g) f

然而,排中的结果是所有命题都是可判定的;这意味着函数可以检查它们是真还是假。 这个函数无法编译,但它仍然存在。 这可用于定义非参数的多态函数:

open Classical in noncomputable def nonParametric {α : _} (xs : List α) : List α := if α = Nat then [] else xs

这个函数的存在与“自由定理”相矛盾:

theorem unit_not_nat : Unit Nat := Unit Nat eq:Unit = NatFalse eq:Unit = NatallEq: (a b : Nat), a = bFalse eq:Unit = NatallEq:0 = 1False All goals completed! 🐙 example : False := False this:(nonParametric List.map fun x => 42) = (List.map fun x => 42) nonParametricFalse this:((fun xs => if Nat = Nat then [] else xs) List.map fun x => 42) = (List.map fun x => 42) fun xs => if Unit = Nat then [] else xsFalse this:((fun xs => []) List.map fun x => 42) = (List.map fun x => 42) fun xs => xsFalse this✝:((fun xs => []) List.map fun x => 42) = (List.map fun x => 42) fun xs => xsthis:((fun xs => []) List.map fun x => 42) [()] = ((List.map fun x => 42) fun xs => xs) [()]False All goals completed! 🐙

8.3. 减少🔗

即使一致的公理也会造成困难。 定义等价 标识项模归约规则。 ι-reduction规则指定了递归器和构造函数的交互;因为公理不是构造函数,所以它不适用于它们。 通常,没有自由变量的项会简化为构造函数的应用,但公理可能会导致它们“卡住”,从而导致项很大。

Axioms and Stuck Reduction

使用公理向 Nat 添加额外的 0 会导致一些定义归约陷入困境。 在此示例中,两个 Nat.succ 构造函数通过归约成功移至项外,但 Nat.rec 在遇到 Nat.otherZero 后无法取得进一步的进展。

axiom Nat.otherZero : Nat ((Nat.rec fun x => x, PUnit.unit (fun n n_ih => fun x => (n_ih.1 x).succ, n_ih) Nat.otherZero).1 4).succ.succ#reduce 4 + (Nat.otherZero + 2)
((Nat.rec fun x => x, PUnit.unit (fun n n_ih => fun x => (n_ih.1 x).succ, n_ih) Nat.otherZero).1 4).succ.succ

此外,Lean 编译器无法生成公理代码。 在运行时,Lean 值必须由内存中的具体数据表示,但公理没有具体表示。 包含依赖公理的非证明代码的定义必须标记为 noncomputable 并且无法编译。

Axioms and Compilation

使用公理向 Nat 添加额外的 0 会使使用它的函数无法编译。 特别是,List.length' 返回公理 Nat.otherZero 而不是 Nat.zero 作为空列表的长度。

axiom Nat.otherZero : Nat def `Nat.otherZero` not supported by code generator; consider marking definition as `noncomputable`List.length' : List α Nat | [] => Nat.otherZero | _ :: _ => Unknown identifier `xs.length`xs.length
`Nat.otherZero` not supported by code generator; consider marking definition as `noncomputable`

在证明中而不是在程序中使用的公理不会阻止函数的编译。 编译器不会生成证明代码,因此证明中的公理没有问题。 nextOddNat 计算下一个奇数,该奇数可以是数字本身或更大的数字:

def nextOdd (k : Nat) : { n : Nat // n % 2 = 1 (n = k n = k + 1) } where val := if k % 2 = 1 then k else k + 1 property := k:Nat(if k % 2 = 1 then k else k + 1) % 2 = 1 ((if k % 2 = 1 then k else k + 1) = k (if k % 2 = 1 then k else k + 1) = k + 1) k:Nath✝:k % 2 = 1(if k % 2 = 1 then k else k + 1) % 2 = 1 ((if k % 2 = 1 then k else k + 1) = k (if k % 2 = 1 then k else k + 1) = k + 1)k:Nath✝:¬k % 2 = 1(if k % 2 = 1 then k else k + 1) % 2 = 1 ((if k % 2 = 1 then k else k + 1) = k (if k % 2 = 1 then k else k + 1) = k + 1) k:Nath✝:k % 2 = 1(if k % 2 = 1 then k else k + 1) % 2 = 1 ((if k % 2 = 1 then k else k + 1) = k (if k % 2 = 1 then k else k + 1) = k + 1)k:Nath✝:¬k % 2 = 1(if k % 2 = 1 then k else k + 1) % 2 = 1 ((if k % 2 = 1 then k else k + 1) = k (if k % 2 = 1 then k else k + 1) = k + 1) k:Nath✝:¬k % 2 = 1(k + 1) % 2 = 1 k:Nath✝:¬k % 2 = 1(k + 1) % 2 = 1 All goals completed! 🐙

策略证明生成一个传递依赖于三个公理的项:

'nextOdd' depends on axioms: [propext, Classical.choice, Quot.sound]#print axioms nextOdd
'nextOdd' depends on axioms: [propext, Classical.choice, Quot.sound]

因为它们只出现在证明中,所以编译器生成代码没有问题:

(5, 5)#eval (nextOdd 4, nextOdd 5)
(5, 5)

8.4. 标准公理🔗

Lean 中有七个标准公理。前三个公理是 Lean 中数学计算方式的重要部分:

  • Classical.choice.{u} {α : Sort u} : Nonempty α α
  • propext {a b : Prop} : (a b) a = b
  • Quot.sound.{u} {α : Sort u} {r : α α Prop} {a b : α} : r a b Quot.mk r a = Quot.mk r b

All three of these axioms are discussed in the book Theorem Proving in Lean.

The axiom sorryAx is used as part of the implementation of the sorry tactic and sorry term. Uses of this axiom are not intended to occur in finished proofs, as it can be used to prove anything:

  • sorryAx {α : Sort u} (synthetic := true) : α

最后三个公理就其数学内容而言并不真正存在;从数学的角度来看,他们证明了一些微不足道的陈述:

  • Lean.trustCompiler : True
  • Lean.ofReduceBool (a b : Bool) : Lean.reduceBool a = b a = b
  • Lean.ofReduceNat (a b : Nat) : Lean.reduceNat a = b a = b

These axioms instead track proofs that depend on the correctness of the entire compiler, and not just on the much smaller kernel.

Creating and Tracking Proofs That Trust the Compiler

The functions Lean.reduceBool and Lean.reduceNat can be invoked to have the compiler perform a calculation; this can greatly improve performance of implementations of proof by reflection.

def largeNumber : Nat := `Lean.reduceNat` has been deprecated: in-kernel native reduction is deprecated; assert native evaluations with axioms insteadLean.reduceNat (230_000 + 4_500 + 1_000_067)

The resulting term depends on the axiom Lean.trustCompiler in order to track the fact that this calculation depends on the correctness of the compiler.

'largeNumber' depends on axioms: [Lean.trustCompiler]#print axioms largeNumber
'largeNumber' depends on axioms: [Lean.trustCompiler]
Axioms and the native_decide Tactic

Instead of appealing to Lean.trustCompiler, the native_decide tactic creates a bespoke axiom for each invocation. This allows each axiom to be audited for the precise statement that it proves.

def bigSum : (List.range 1_001).sum = 500_500 := (List.range 1001).sum = 500500 All goals completed! 🐙 'bigSum' depends on axioms: [bigSum._native.native_decide.ax_1]#print axioms bigSum
'bigSum' depends on axioms: [bigSum._native.native_decide.ax_1]

The axiom's type can be checked directly:

bigSum._native.native_decide.ax_1 : decide ((List.range 1001).sum = 500500) = true#check bigSum._native.native_decide.ax_1
bigSum._native.native_decide.ax_1 : decide ((List.range 1001).sum = 500500) = true

The command Lean.Parser.Command.printAxioms : commandPrints the axioms used by a declaration, directly or indirectly. Please consult [the reference manual](https://lean-lang.org/doc/reference/4.31.0/find/?domain=Verso.Genre.Manual.section&name=validating-proofs) to understand the significance of the output. #print axioms, followed by a defined identifier, displays all the axioms that a definition transitively relies on. In other words, if a proof uses another proof, which itself uses an axiom, then the axiom is reported by Lean.Parser.Command.printAxioms : commandPrints the axioms used by a declaration, directly or indirectly. Please consult [the reference manual](https://lean-lang.org/doc/reference/4.31.0/find/?domain=Verso.Genre.Manual.section&name=validating-proofs) to understand the significance of the output. #print axioms for both.

This can be used to audit the assumptions made by a proof, for instance detecting that a proof transitively depends on the sorry tactic.

def declaration uses `sorry`lazy : 4 == 2 + 1 + 1 := (4 == 2 + 1 + 1) = true All goals completed! 🐙 'lazy' depends on axioms: [sorryAx]#print axioms lazy
'lazy' depends on axioms: [sorryAx]
Printing Axioms of Simple Definitions

Consider the following three constants:

def addThree (n : Nat) : Nat := 1 + n + 2 theorem excluded_middle (P : Prop) : P ¬ P := Classical.em P theorem simple_equality (P : Prop) : (P False) = P := or_false P

Regular functions like addThree that we might want to actually evaluation typically do not depend on any axioms:

'addThree' does not depend on any axioms#print axioms addThree
'addThree' does not depend on any axioms

The excluded middle theorem is only true if we use classical reasoning, so the foundation for classical reasoning shows up alongside other axioms:

'excluded_middle' depends on axioms: [propext, Classical.choice, Quot.sound]#print axioms excluded_middle
'excluded_middle' depends on axioms: [propext, Classical.choice, Quot.sound]

Finally, the idea that two equivalent propositions are equal directly relies on propositional extensionality.

'simple_equality' depends on axioms: [propext]#print axioms simple_equality
'simple_equality' depends on axioms: [propext]
Using Lean.Parser.Command.printAxioms : commandPrints the axioms used by a declaration, directly or indirectly. Please consult [the reference manual](https://lean-lang.org/doc/reference/4.31.0/find/?domain=Verso.Genre.Manual.section&name=validating-proofs) to understand the significance of the output. #print axioms with Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` captures the messages generated by the command `cmd` and checks that they match the contents of the docstring. Basic example: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` This checks that there is such an error and then consumes the message. By default, the command captures all messages, but the filter condition can be adjusted. For example, we can select only warnings: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` or only errors ```lean #guard_msgs(error) in example : α := sorry ``` In the previous example, since warnings are not captured there is a warning on `sorry`. We can drop the warning completely with ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` In general, `#guard_msgs` accepts a comma-separated list of configuration clauses in parentheses: ``` #guard_msgs (configElt,*) in cmd ``` By default, the configuration list is `(check all, whitespace := normalized, ordering := exact, positions := false)`. Message filters select messages by severity: - `info`, `warning`, `error`: (non-trace) messages with the given severity level. - `trace`: trace messages - `all`: all messages. The filters can be prefixed with the action to take: - `check` (the default): capture and check the message - `drop`: drop the message - `pass`: let the message pass through If no filter is specified, `check all` is assumed. Otherwise, these filters are processed in left-to-right order, with an implicit `pass all` at the end. Whitespace handling (after trimming leading and trailing whitespace): - `whitespace := exact` requires an exact whitespace match. - `whitespace := normalized` converts all newline characters to a space before matching (the default). This allows breaking long lines. - `whitespace := lax` collapses whitespace to a single space before matching. Message ordering: - `ordering := exact` uses the exact ordering of the messages (the default). - `ordering := sorted` sorts the messages in lexicographic order. This helps with testing commands that are non-deterministic in their ordering. Position reporting: - `positions := true` reports the ranges of all messages relative to the line on which `#guard_msgs` appears. - `positions := false` does not report position info. Substring matching: - `substring := true` checks that the docstring appears as a substring of the output (after whitespace normalization). This is useful when you only care about part of the message. - `substring := false` (the default) requires exact matching (modulo whitespace normalization). Stabilizing output: When messages contain autogenerated names (e.g., metavariables like `?m.47`), the output may differ between runs or Lean versions. Use `set_option pp.mvars.anonymous false` to replace anonymous metavariables with `?_` while preserving user-named metavariables like `?a`. Alternatively, `set_option pp.mvars false` replaces all metavariables with `?_`. Similarly, `set_option pp.fvars.anonymous false` replaces loose free variable names like `_fvar.22` with `_fvar._`. For example, `#guard_msgs (error, drop all) in cmd` means to check errors and drop everything else. The command elaborator has special support for `#guard_msgs` for linting. The `#guard_msgs` itself wants to capture linter warnings, so it elaborates the command it is attached to as if it were a top-level command. However, the command elaborator runs linters for *all* top-level commands, which would include `#guard_msgs` itself, and would cause duplicate and/or uncaptured linter warnings. The top-level command elaborator only runs the linters if `#guard_msgs` is not present. #guard_msgs

You can use Lean.Parser.Command.printAxioms : commandPrints the axioms used by a declaration, directly or indirectly. Please consult [the reference manual](https://lean-lang.org/doc/reference/4.31.0/find/?domain=Verso.Genre.Manual.section&name=validating-proofs) to understand the significance of the output. #print axioms together with Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` captures the messages generated by the command `cmd` and checks that they match the contents of the docstring. Basic example: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` This checks that there is such an error and then consumes the message. By default, the command captures all messages, but the filter condition can be adjusted. For example, we can select only warnings: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` or only errors ```lean #guard_msgs(error) in example : α := sorry ``` In the previous example, since warnings are not captured there is a warning on `sorry`. We can drop the warning completely with ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` In general, `#guard_msgs` accepts a comma-separated list of configuration clauses in parentheses: ``` #guard_msgs (configElt,*) in cmd ``` By default, the configuration list is `(check all, whitespace := normalized, ordering := exact, positions := false)`. Message filters select messages by severity: - `info`, `warning`, `error`: (non-trace) messages with the given severity level. - `trace`: trace messages - `all`: all messages. The filters can be prefixed with the action to take: - `check` (the default): capture and check the message - `drop`: drop the message - `pass`: let the message pass through If no filter is specified, `check all` is assumed. Otherwise, these filters are processed in left-to-right order, with an implicit `pass all` at the end. Whitespace handling (after trimming leading and trailing whitespace): - `whitespace := exact` requires an exact whitespace match. - `whitespace := normalized` converts all newline characters to a space before matching (the default). This allows breaking long lines. - `whitespace := lax` collapses whitespace to a single space before matching. Message ordering: - `ordering := exact` uses the exact ordering of the messages (the default). - `ordering := sorted` sorts the messages in lexicographic order. This helps with testing commands that are non-deterministic in their ordering. Position reporting: - `positions := true` reports the ranges of all messages relative to the line on which `#guard_msgs` appears. - `positions := false` does not report position info. Substring matching: - `substring := true` checks that the docstring appears as a substring of the output (after whitespace normalization). This is useful when you only care about part of the message. - `substring := false` (the default) requires exact matching (modulo whitespace normalization). Stabilizing output: When messages contain autogenerated names (e.g., metavariables like `?m.47`), the output may differ between runs or Lean versions. Use `set_option pp.mvars.anonymous false` to replace anonymous metavariables with `?_` while preserving user-named metavariables like `?a`. Alternatively, `set_option pp.mvars false` replaces all metavariables with `?_`. Similarly, `set_option pp.fvars.anonymous false` replaces loose free variable names like `_fvar.22` with `_fvar._`. For example, `#guard_msgs (error, drop all) in cmd` means to check errors and drop everything else. The command elaborator has special support for `#guard_msgs` for linting. The `#guard_msgs` itself wants to capture linter warnings, so it elaborates the command it is attached to as if it were a top-level command. However, the command elaborator runs linters for *all* top-level commands, which would include `#guard_msgs` itself, and would cause duplicate and/or uncaptured linter warnings. The top-level command elaborator only runs the linters if `#guard_msgs` is not present. #guard_msgs to ensure that updates to libraries from other projects cannot silently introduce unwanted dependencies on axioms.

For example, if the proof of double_neg_elim below changed in such a way that it used more axioms than those listed, then the Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` captures the messages generated by the command `cmd` and checks that they match the contents of the docstring. Basic example: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` This checks that there is such an error and then consumes the message. By default, the command captures all messages, but the filter condition can be adjusted. For example, we can select only warnings: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` or only errors ```lean #guard_msgs(error) in example : α := sorry ``` In the previous example, since warnings are not captured there is a warning on `sorry`. We can drop the warning completely with ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` In general, `#guard_msgs` accepts a comma-separated list of configuration clauses in parentheses: ``` #guard_msgs (configElt,*) in cmd ``` By default, the configuration list is `(check all, whitespace := normalized, ordering := exact, positions := false)`. Message filters select messages by severity: - `info`, `warning`, `error`: (non-trace) messages with the given severity level. - `trace`: trace messages - `all`: all messages. The filters can be prefixed with the action to take: - `check` (the default): capture and check the message - `drop`: drop the message - `pass`: let the message pass through If no filter is specified, `check all` is assumed. Otherwise, these filters are processed in left-to-right order, with an implicit `pass all` at the end. Whitespace handling (after trimming leading and trailing whitespace): - `whitespace := exact` requires an exact whitespace match. - `whitespace := normalized` converts all newline characters to a space before matching (the default). This allows breaking long lines. - `whitespace := lax` collapses whitespace to a single space before matching. Message ordering: - `ordering := exact` uses the exact ordering of the messages (the default). - `ordering := sorted` sorts the messages in lexicographic order. This helps with testing commands that are non-deterministic in their ordering. Position reporting: - `positions := true` reports the ranges of all messages relative to the line on which `#guard_msgs` appears. - `positions := false` does not report position info. Substring matching: - `substring := true` checks that the docstring appears as a substring of the output (after whitespace normalization). This is useful when you only care about part of the message. - `substring := false` (the default) requires exact matching (modulo whitespace normalization). Stabilizing output: When messages contain autogenerated names (e.g., metavariables like `?m.47`), the output may differ between runs or Lean versions. Use `set_option pp.mvars.anonymous false` to replace anonymous metavariables with `?_` while preserving user-named metavariables like `?a`. Alternatively, `set_option pp.mvars false` replaces all metavariables with `?_`. Similarly, `set_option pp.fvars.anonymous false` replaces loose free variable names like `_fvar.22` with `_fvar._`. For example, `#guard_msgs (error, drop all) in cmd` means to check errors and drop everything else. The command elaborator has special support for `#guard_msgs` for linting. The `#guard_msgs` itself wants to capture linter warnings, so it elaborates the command it is attached to as if it were a top-level command. However, the command elaborator runs linters for *all* top-level commands, which would include `#guard_msgs` itself, and would cause duplicate and/or uncaptured linter warnings. The top-level command elaborator only runs the linters if `#guard_msgs` is not present. #guard_msgs command would report an error.

theorem double_neg_elim (P : Prop) : (¬ ¬ P) = P := propext Classical.not_not /-- info: 'double_neg_elim' depends on axioms: [propext, Classical.choice, Quot.sound] -/ #guard_msgs (whitespace := lax) in #print axioms double_neg_elim