This is a reasonable approach. You prove this by "relaxing" a
to be a real number, namely proving 2 <= (a : Real) <= 3
and then using that to deduce 2 <= (a : Int) <= 3
. I'll let you handle how to prove 2 <= (a : Real) <= 3
, but let's talk about converting between Int
and Real
, and more generally casting from one type to another.
Coercion basics
If a
has type Int
and you write a : Real
, what you are telling Lean to do is coerce a
from Int
to Real
. So a
is replaced with ↑a
. This uparrow symbol (written \u
or \uparrow
in the Lean vs code plugin) is shorthand for @Int.cast ℝ Real.instIntCast a
. You can find this out by hovering over ↑a
in the infoview. It also gives this helpful message:
↑x
represents a coercion, which converts x
of type α
to type β
, using typeclasses to resolve a suitable conversion function. You can often leave the ↑
off entirely, since coercion is triggered implicitly whenever there is a type error, but in ambiguous cases it can be useful to use ↑
to disambiguate between e.g. ↑x + ↑y
and ↑(x + y)
.
As this message suggests, if Lean is expecting a real number, as in a < Real.pi
, then a
, ↑a
, and (a : ℝ)
are all the same.
#check ∀ a : ℤ, a < Real.pi -- ∀ (a : ℤ), ↑a < Real.pi
#check ∀ a : ℤ, ↑a < Real.pi -- ∀ (a : ℤ), ↑a < Real.pi
#check ∀ a : ℤ, (a : ℝ) < Real.pi -- ∀ (a : ℤ), ↑a < Real.pi
One case where a
alone wouldn't work is when the expression is valid as either an Int
or a Real
. In that case, you have to specify the coercion using (a : ℝ)
in at least one place.
#check ∀ a : ℤ, ((a : ℝ) / 2) * 2 = a -- ((a : ℝ) / 2) * 2 = a
It is not enough here to just use ↑a
since Lean doesn't know what to coerce to. For example, this doesn't coerce at all:
#check ∀ a : ℤ, ↑a / 2 * 2 = a -- ∀ (a : ℤ), a / 2 * 2 = a
Conversely, Lean seems to apply ↑a
as far inside as possible. So sometimes one needs to explicitly use ↑
to specify where the conversion should be.
#check ∀ a : ℤ, ((a + a) : ℝ) = (a : ℝ) + (a : ℝ)
-- ∀ (a : ℤ), ↑a + ↑a = ↑a + ↑a
#check ∀ a : ℤ, (↑(a + a) : ℝ) = a + a
-- ∀ (a : ℤ), ↑(a + a) = ↑a + ↑a
Coersion can change the truth value of a theorem
The reason that
have ha : a ^ 2 - 5 * a + 5 = (a - 5/2) ^ 2 - 5/4 := by ring
failed, is that the theorem is false (and ring
tells you this), because for the integers, 5/4 = 1
and 5/2 = 2
.
#eval (5: ℤ ) / 4 -- 1
#eval (5: ℤ ) / 2 -- 2
(This is similar to say 5 // 4
and 5 // 2
in python if you are familiar with integer division in typical programming languages.)
So this brings us to an important point. Just because you prove a theorem for (a : ℝ)
doesn't mean it still holds for a
.
How to go back and forth between coercions?
It shouldn't be hard for you to prove 2 <= (a : Real)
and (a : Real) <= 3
, but that isn't your goal. You want 2 <= a
and a <= 3
(which since a
is an integer implies that a = 2
or a = 3
).
This is a common case in coercions. While it isn't true in general that any theorem of (a : Real)
is a theorem of a
, it is true that many functions and relations are ↑
-invariant (meaning R_ℤ a b ↔ R_ℝ ↑a ↑b
) and other functions are ↑
-equivariant (meaning ↑(f_ℤ a b) = f_ℝ ↑a ↑b
).
These theorems should be readily available in Mathlib, but instead of looking them up, there are tactics to help.
norm_cast
normalizes coercions (removing them if possible).
example (a : ℤ) (h : a < 9) : (a : ℝ) < 10 := by
norm_cast -- normalizes coercions (removing them if possible) in goal
/-
a : ℤ
h : a < 9
⊢ a < 10
-/
linarith [h]
example (a : ℤ) (h : (a : ℝ) < 9) : a < 10 := by
norm_cast at h -- normalizes coersions (removing them if possible) in the hypotheses `h`
/-
a : ℤ
h : a < 9
⊢ a < 10
-/
linarith [h]
push_cast
pushes coercions inward (again removing them if possible).
example (a b : ℤ) (h : a + b < 9) : ↑ (a + b) < 10 := by
push_cast -- pushes coercions inward (removing them if possible) in the goal
/-
a b : ℤ
h : a + b < 9
⊢ a + b < 10
-/
linarith [h]
example (a b : ℤ) (h : ↑ (a + b) < 9) : a + b < 10 := by
push_cast at h -- pushes coercions inward (removing them if possible) in the hypothesis
/-
a b : ℤ
h : a + b < 9
⊢ a + b < 10
-/
linarith [h]
exact_mod_cast
and apply_mod_cast
are versions of exact
and apply
which also normalizes coercions.
example (a : ℤ) (h : a < 10) : (a : ℝ) < 10 := by
exact_mod_cast h -- like `exact` but normalizes coercions first
example (a : ℤ) (h : (a : ℝ) < 10) : a < 10 := by
exact_mod_cast h -- like `exact` but normalizes coercions first
example (a : ℤ) (h : (a : ℝ) < 10) : a < 10 := by
apply_mod_cast h -- like `apply` but normalizes coercions first
rify
converts goals on ℕ
, ℤ
, ℚ
, etc. to ℝ. There is also qify
for rationals and zify
for integers. The zify
, qify
, rify
tactics require Mathlib, while the other ..._cast
tactics are available in base Lean.
example (a : ℤ) (h : (a : ℝ) < 9) : a < 10 := by
rify -- converts goal to reals (use `zify` for ℤ and `qify` for ℚ)
/-
a : ℤ
h : ↑a < 9
⊢ ↑a < 10
-/
linarith [h]
example (a : ℤ) (h : (a : ℝ) < 10) : a < 10 := by
rify [h] -- `rify` using `h` as a simp lemma
Other coercions
There are a lot of coercions in Lean. They come up in natural settings where one wants to interpret one type as another because of some canonical embedding (usually a homomorphism).
There are coercions from ℕ to ℚ to ℝ to ℂ; coercions to and from Nat
and Fin n
; coercions from fields to rings to groups (the forgetful functor); coercions from ℕ to rings, and many more.
New coercions have to be registered so Lean knows about them. For more information see Coercions using Type Classes in Theorem Proving in Lean 4.