rfl — Reflexivity
rfl proves that something equals itself. It's the most fundamental tactic in Lean 4 and often the first you'll learn. Despite its simplicity, understanding when and why it works is essential for mastering proof writing.
The Basic Idea
Every value equals itself. This is the reflexivity property of equality—one of the most basic axioms in mathematics. In Lean, rfl (short for "reflexivity") is how you prove that two expressions are equal when they compute to the same value:
1-- Anything equals itself2example : 5 = 5 := rfl3example : "hello" = "hello" := rfl4example : [1, 2, 3] = [1, 2, 3] := rfl56-- Works for any type7example : true = true := rfl8example : () = () := rfl -- Unit typeBut rfl is more powerful than it looks. It works whenever Lean can compute both sides to the same value. This means you can prove equalities that don't look identical at first glance:
1-- These compute to the same value2example : 2 + 2 = 4 := rfl -- 2 + 2 computes to 43example : 10 - 3 = 7 := rfl -- 10 - 3 computes to 74example : 2 * 3 + 1 = 7 := rfl -- 2 * 3 + 1 → 6 + 1 → 756-- String operations7example : "hel" ++ "lo" = "hello" := rfl89-- List operations10example : [1, 2] ++ [3] = [1, 2, 3] := rfl11example : [1, 2, 3].head? = some 1 := rflrfl works when both sides of an equality compute (or "reduce") to the same term. Lean evaluates as much as it can before comparing. This is called definitional equality.When to Use rfl
Reach for rfl in these situations:
- Closing trivial goals: When both sides of an equality are identical or compute to the same thing
- After rewriting: When
rwtransforms the goal tox = x - Verifying function definitions: To confirm that a function produces the expected output for specific inputs
- Testing computed values: To validate that complex expressions evaluate correctly
1-- Definitional equality: both sides compute to the same thing2example : (fun x => x + 1) 5 = 6 := rfl -- Function application34example : [1, 2, 3].length = 3 := rfl -- Method call56example : (if true then 1 else 2) = 1 := rfl -- Conditional evaluation78def double (n : Nat) : Nat := n * 29example : double 5 = 10 := rfl -- User-defined function1011-- Pattern matching computes12def isZero : Nat → Bool13 | 0 => true14 | _ => false15example : isZero 0 = true := rfl16example : isZero 7 = false := rflWhen rfl Fails
rfl can't prove things that require reasoning beyond computation. If Lean can't directly evaluate both sides to the exact same term, rfl will fail:
1-- These require actual reasoning, not just computation23-- example : a + b = b + a := rfl 4-- ❌ Fails! Lean doesn't know what a and b are, can't compute56-- example : n + 0 = n := rfl7-- ❌ Fails for arbitrary n (works for specific n like 5)89-- example : n * 1 = n := rfl10-- ❌ Fails! Requires induction to prove for all n1112-- example : List.reverse (List.reverse xs) = xs := rfl13-- ❌ Fails! Can't compute without knowing xsWhen rfl fails, you need other tactics:
simp— for simplification using known lemmasrw— to rewrite using equalitiesring— for algebraic identitiesomega— for linear arithmetic
rfl only works for definitional equality—when things compute to the same value. For propositional equality (things that are equal but don't directly compute), you need other tactics.Common Mistakes
Here are pitfalls beginners often encounter with rfl:
Mistake 1: Expecting rfl to prove algebraic identities
1-- ❌ Wrong: rfl can't prove this2-- example (n : Nat) : n + 0 = n := rfl34-- ✓ Correct: use simp or the specific lemma5example (n : Nat) : n + 0 = n := by simp6example (n : Nat) : n + 0 = n := Nat.add_zero nMistake 2: Confusing similar-looking expressions
1-- These look the same but aren't definitionally equal!2-- example (xs : List Nat) : xs ++ [] = xs := rfl -- ❌ Fails34-- The definition of ++ recurses on the first list5-- so xs ++ [] doesn't simplify without knowing xs6example (xs : List Nat) : xs ++ [] = xs := by simp -- ✓ WorksMistake 3: Forgetting order matters for some operations
1-- 0 + n and n + 0 behave differently!2example (n : Nat) : 0 + n = n := rfl -- ✓ Works! (by definition of +)3-- example (n : Nat) : n + 0 = n := rfl -- ❌ Fails (requires lemma)rfl as a Tactic vs Term
rfl can be used both as a proof term and as a tactic. Both are correct, but they behave slightly differently:
1-- As a term (no 'by') - direct proof term2example : 2 + 2 = 4 := rfl34-- As a tactic (with 'by') - enters tactic mode first5example : 2 + 2 = 4 := by rfl67-- The tactic version gives better error messages when it fails8-- It also allows you to add other tactics before it if needed9example : 2 + 2 = 4 := by10 -- could add 'show 4 = 4' or other tactics here11 rflRelated: Eq.refl and @rfl
rfl is actually notation for Eq.refl _, where Lean infers the value. Understanding this helps when you need more control:
1-- These are all equivalent2example : 5 = 5 := rfl3example : 5 = 5 := Eq.refl 54example : 5 = 5 := @rfl Nat 556-- Eq.refl has type: ∀ (a : α), a = a7#check @Eq.refl -- {α : Sort u} → (a : α) → a = a89-- Sometimes you need @rfl to help type inference10example : ([] : List Nat) = [] := @rfl (List Nat) []Deep Dive: Definitional vs Propositional Equality
Lean has two notions of equality, and understanding the difference is crucial:
- Definitional equality: Two terms compute to the same value. Checked automatically by Lean's kernel.
rflproves this. - Propositional equality: Two terms are provably equal via some chain of reasoning. Requires explicit proof with tactics or lemmas.
For example:
2 + 2and4are definitionally equaln + 0andnare only propositionally equal(proven by the lemmaNat.add_zero)a + bandb + aare only propositionally equal(proven byNat.add_comm)
Definitional equality is "built-in" and checked automatically. Propositional equality requires you to prove it explicitly.
Real-World Examples
Validating data transformations
1-- Verify JSON-like value transformations2def Config := List (String × Nat)34def defaultConfig : Config := [("timeout", 30), ("retries", 3)]56example : defaultConfig.length = 2 := rfl7example : (defaultConfig.lookup "timeout") = some 30 := rflTesting parsing functions
1-- Quick unit tests using rfl2def parseDigit : Char → Option Nat3 | '0' => some 04 | '1' => some 15 | '2' => some 26 | _ => none78example : parseDigit '1' = some 1 := rfl9example : parseDigit 'a' = none := rflClosing goals after rewriting
1-- Common pattern: rewrite then close with rfl2example (a b : Nat) (h : a = b) : b = a := by3 rw [h] -- Goal becomes: b = b4 rfl -- Now trivially true56example (x y z : Nat) (h1 : x = y) (h2 : y = z) : x = z := by7 rw [h1, h2] -- Goal becomes: z = z8 rflPerformance: native_decide
For decidable propositions with large computations, rfl can be slow because it runs in Lean's type checker. For better performance, usenative_decide which compiles to native code:
1-- This might be slow with rfl for large numbers2example : 123456 + 789012 = 912468 := by native_decide34-- native_decide uses compiled code, which is much faster5-- Useful for large computations you want to verifySummary
| Situation | Use rfl? |
|---|---|
Goal is x = x | ✅ Yes |
| Both sides compute to same value | ✅ Yes |
Involves variables (like n + 0 = n) | ❌ No, use simp |
Algebraic identity (like a + b = b + a) | ❌ No, use ring or lemma |
Close the goal using computation and rfl.
1example : (3 * 4) + 2 = 14 := by2 rflVerify that this function works correctly for the given input.
1def greet (name : String) : String := "Hello, " ++ name ++ "!"23example : greet "World" = "Hello, World!" := by4 rflThis proof fails with rfl. Can you explain why and fix it with the right tactic?
1-- This doesn't work:2-- example (n : Nat) : n + 0 = n := rfl34-- Fix it:5example (n : Nat) : n + 0 = n := by6 simp -- or: exact Nat.add_zero n