The Lean Language Reference

8. Axioms🔗

Axioms are postulated constants. While the axiom's type must itself be a type (that is, it must have type Sort u), there are no further requirements. Axioms do not reduce to other terms.

Axioms can be used to experiment with the consequences of an idea before investing the time required to construct a model or prove a theorem. They can also be used to adopt reasoning principles that can't otherwise be accessed in Lean's type theory; Lean itself provides three such axioms that are known to be consistent. However, axioms should be used with caution: axioms that are inconsistent with one another, or just false, undermine the very foundations of proofs. Lean automatically tracks the axioms that each proof depends on so that they can be audited.

8.1. Axiom Declarations🔗

Axioms declarations include a name and a type:

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 declarations may be modified with all possible declaration modifiers. Documentation comments, attributes, private, and protected have the same meaning as for other declarations. The modifiers partial, nonrec, noncomputable and unsafe have no effect.

8.2. Consistency🔗

Using axioms is risky. Because they introduce a new constant of any type, and an inhabitant of a type that is a proposition counts as a proof of the proposition, axioms can be used to prove even false propositions. Any proof that relies on an axiom can be trusted only to the extent that the axiom is both true and consistent with the other axioms used. By their very nature, Lean cannot check whether new axioms are consistent; please exercise care when adding axioms.

Inconsistencies From Axioms

Axioms may introduce inconsistency, either alone or in combination with other axioms.

Assuming a false statement allows any statement at all to be proved:

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

Inconsistency may also arise from axioms that are incompatible with other properties of Lean. For example, parametricity is a powerful reasoning technique when used in languages that support it, but it is not compatible with Lean's standard axioms. If parametricity held, then the “free theorem” from the introduction to Wadler's Theorems for Free (1989), which describes a technique for using parametricity to derive theorems about polymorphic functions, would be true. As an axiom, it reads:

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

However, a consequence of excluded middle is that all propositions are decidable; this means that a function can check whether they are true or false. This function can't be compiled, but it still exists. This can be used to define polymorphic functions that are not parametric:

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

The existence of this function contradicts the “free theorem”:

theorem unit_not_nat : Unit Nat := UnitNat eq:Unit = NatFalse eq:Unit = NatallEq:∀ (a b : Nat), a = bFalse eq:Unit = NatallEq:0 = 1False All goals completed! 🐙 example : False := False this:(nonParametricList.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🔗

Even consistent axioms can cause difficulties. Definitional equality identifies terms modulo reduction rules. The ι-reduction rule specifies the interaction of recursors and constructors; because axioms are not constructors, it does not apply to them. Ordinarily, terms without free variables reduce to applications of constructors, but axioms can cause them to get “stuck,” resulting in large terms.

Axioms and Stuck Reduction

Adding an addtional 0 to Nat with an axiom results in some definitional reductions getting stuck. In this example, two Nat.succ constructors are successfully moved to the outside of the term by reduction, but Nat.rec is unable to make further progress after encountering 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

Furthermore, the Lean compiler is not able to generate code for axioms. At runtime, Lean values must be represented by concrete data in memory, but axioms do not have a concrete representation. Definitions that contain non-proof code that relies on axioms must be marked noncomputable and can't be compiled.

Axioms and Compilation

Adding an addtional 0 to Nat with an axiom makes it so functions that use it can't be compiled. In particular, List.length' returns the axiom Nat.otherZero instead of Nat.zero. The function is marked partial to avoid further error messages from termination checking.

axiom Nat.otherZero : Nat partial def failed to compile definition, consider marking it as 'noncomputable' because it depends on 'Nat.otherZero', and it does not have executable codeList.length' : List α Nat | [] => Nat.otherZero | _ :: xs => xs.length' + 1
failed to compile definition, consider marking it as 'noncomputable' because it depends on 'Nat.otherZero', and it does not have executable code

Axioms used in proofs rather than programs do not prevent a function from being compiled. The compiler does not generate code for proofs, so axioms in proofs are no problem. nextOdd computes the next odd number from a Nat, which may be the number itself or one greater:

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

The by_cases tactic uses the Law of the Excluded Middle, which is proved in the standard library using the Axiom of Choice. Indeed, this definition uses three axioms:

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

Because they occur only in a proof, the compiler has no problem generating code:

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

The command Lean.Parser.Command.printAxioms : command#print axioms displays all the axioms that a declaration 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 : command#print axioms for both. This can be used to audit the assumptions made by a proof. 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 `(all, whitespace := normalized, ordering := exact)`. Message filters (processed in left-to-right order): - `info`, `warning`, `error`: capture messages with the given severity level. - `all`: capture all messages (the default). - `drop info`, `drop warning`, `drop error`: drop messages with the given severity level. - `drop all`: drop every message. 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. For example, `#guard_msgs (error, drop all) in cmd` means to check warnings 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, it can also ensure that updates to libraries from other projects don't silently introduce unwanted dependencies on axioms.

8.5. Standard Axioms🔗

Lean includes the following mathematical axioms:

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

Three additional axioms allow the Lean kernel to invoke code generated by the compiler, rather than using its internal reduction engine. This can greatly improve performance of implementations of proof by reflection. Rather than using these axioms directly, they are usually invoked via the native_decide tactic. Both Lean.reduceBool and Lean.reduceNat contain references to Lean.trustCompiler, which ensures that the fact that a proof trusts the correctness of the compiler is tracked.

These axioms do not truly exist for their mathematical content; after all, Lean.reduceBool and Lean.reduceNat are essentially the identity function. However, they allow the use of compiled code in proofs to be carefully controlled, and tracking them as axioms allows Lean.Parser.Command.printAxioms : command#print axioms to be used to audit theorems.

  • 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

Finally, 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, and this can be confirmed using Lean.Parser.Command.printAxioms : command#print axioms.