Zulip Chat Archive
Stream: new members
Topic: how to explicitly rfl for ℚ?
Yongyi Chen (Dec 09 2023 at 18:23):
Let's say you want to prove
example : (1 / 3 : ℚ) + 2 / 3 = 1
without any tactics. Now, the tactic by rfl
works and expands using by?
to Eq.refl (1 / 3 + 2 / 3)
but unfortunately this does not type check in Lean.
Of course, 1/3 + 2/3
as output by the pretty printer is not the right expression, because those literals being interpreted in the world of naturals, so I tried Eq.refl ((1 / 3 : ℚ) + 2 / 3)
instead. Still nope.
by linarith
and by norm_num
expand to horrible messes which, unfortunately, suffer from the same Eq.refl
issues as above.
Yongyi Chen (Dec 09 2023 at 18:25):
Interestingly, by decide
does not work here. Must be a recent breakage, or am I dreaming?
Eric Wieser (Dec 09 2023 at 18:28):
I assume your question is this:
import Mathlib.Data.Rat.Basic
example : (1 / 3 : ℚ) + 2 / 3 = 1 := by show_term rfl -- works
example : (1 / 3 : ℚ) + 2 / 3 = 1 := Eq.refl ((1 / 3 : ℚ) + 2 / 3) -- fails: why?
example : (1 / 3 : ℚ) + 2 / 3 = 1 := rfl -- fails: why?
Eric Wieser (Dec 09 2023 at 18:28):
That is, why by rfl
succeeds but seems to generate a term that fails on its own
Yongyi Chen (Dec 09 2023 at 18:29):
Well, kind of. As in, I'm familiar with the terms generated by show_term
often failing, and that might be a whole (already known) question on its own, but my specific question here is how to actually prove the equality with a term.
Eric Wieser (Dec 09 2023 at 18:31):
example : (1 / 3 : ℚ) + 2 / 3 = 1 := by with_unfolding_all exact rfl
works
Yongyi Chen (Dec 09 2023 at 18:32):
Apparently that just show_term
s to rfl
which does not work, sadly.
Eric Wieser (Dec 09 2023 at 18:32):
Right, the point is that the term is valid from the kernel's point of view, but the elaborator has to be tricked via with_unfolding_all
Yongyi Chen (Dec 09 2023 at 18:34):
Oh, I see. So rfl
technically is valid but doesn't compile because something was marked as irreducible
? So I guess there is no reasonable (without going into tactic mode) term that can prove that 1/3 + 2/3 = 1?
Eric Wieser (Dec 09 2023 at 18:44):
It seems like you have to go into tactic mode, turn off irreducibility checking, then go back into term mode; the rfl
in my proof is the term-mode rfl
Eric Wieser (Dec 09 2023 at 18:45):
Though something very strange does seem to be happening here; I can't work out where Lean is recording the decision to ignore reducibility
Eric Wieser (Dec 09 2023 at 18:46):
I couldn't get this to work:
elab "with_unfolding_all% " y:term : term <= expectedType =>
withTransparency TransparencyMode.all <| (Lean.Elab.Term.elabTerm y expectedType)
example : (1 / 3 : ℚ) + 2 / 3 = 1 := with_unfolding_all% rfl -- fails
Kyle Miller (Dec 09 2023 at 20:56):
If your goal has no free variables, the eq_refl
tactic uses the kernel checker rather than the elaborator's:
example : (1 / 3 : ℚ) + 2 / 3 = 1 := by eq_refl
Yongyi Chen (Dec 09 2023 at 20:57):
That's basically what rfl
calls, right?
macro "rfl" : tactic => `(tactic| eq_refl)
Kyle Miller (Dec 09 2023 at 20:58):
@Eric Wieser I'm guessing with your example that the example
command is itself ensuring that the term has the right type. You can insert a type hint:
example : (1 / 3 : ℚ) + 2 / 3 = 1 := with_unfolding_all% id rfl
(You can also get your elaborator to insert a type hint, via an id
, with mkExpectedTypeHint
)
Kyle Miller (Dec 09 2023 at 20:59):
@Yongyi Chen I forgot that rfl
was different from exact rfl
. I think I learned about eq_refl
from a thread where I learned and immediately forgot that.
Yongyi Chen (Dec 09 2023 at 22:54):
So after thinking about this, I think that Rat.add and siblings should not be marked as irreducible, so that simp
and decide
and rfl
can work properly. (Currently simp
won't even simplify rational expressions.)
Eric Wieser (Dec 09 2023 at 22:58):
Note that norm_num
works fine here, though that isn't necessarily an argument for keeping the irreducibility
Eric Wieser (Dec 09 2023 at 22:59):
(simp can't reasonably be expected to work here, it's not possible to write a lemma that matches arbitrary pairs of digits)
Yongyi Chen (Dec 09 2023 at 23:02):
Eric Wieser said:
(simp can't reasonably be expected to work here, it's not possible to write a lemma that matches arbitrary pairs of digits)
I guess so, since simp
doesn't simplify (1 : ℤ) + (2 : ℤ)
either.
Eric Rodriguez (Dec 09 2023 at 23:06):
decide should work regardless of irreducibility I thought
Yongyi Chen (Dec 09 2023 at 23:07):
Doesn't! But if I make a local copy of the Rat
file and remove the irreducible
modifiers on mul
and inv
, then it does.
Yongyi Chen (Dec 09 2023 at 23:19):
By the way, I solved the original task!
theorem ddd : (1 / 3 : ℚ) + 2 / 3 = 1 := Rat.div_def 1 _ ▸ Rat.div_def 2 _ ▸ Rat.inv_def _ ▸ Rat.mul_def _ _ ▸ Rat.mul_def 2 _ ▸ Rat.add_def' _ _ ▸ rfl
Eric Wieser (Dec 09 2023 at 23:23):
Eric Rodriguez said:
decide should work regardless of irreducibility I thought
decide
relies on the decidability instance being sufficiently reducible in some way
Kevin Buzzard (Dec 10 2023 at 11:35):
I don't see the logic in changing reducibility settings just so rfl can do a job which norm_num
was designed to do...
Eric Wieser (Dec 10 2023 at 12:47):
But nor do I think that "norm_num
doesn't care" is a good answer to "why are Rat operations irreducible?"
Yongyi Chen (Dec 10 2023 at 16:44):
I see linarith
and norm_num
as sledgehammers for the purpose of deciding equality of easily decidable things like integers and rationals, mainly because they produce very long (and slow) code under the hood.
Ruben Van de Velde (Dec 10 2023 at 17:28):
norm_num
is the minimal supported approach to proving things about concrete numbers
Ruben Van de Velde (Dec 10 2023 at 17:30):
If there's ways in which it's suboptimal, it's better to improve it rather than trying to work around it
Yongyi Chen (Dec 10 2023 at 17:46):
I don't think this is an argument for why decide
should fail to decide that two rational numbers are equal.
Kyle Miller (Dec 10 2023 at 17:52):
norm_num
isn't a sledgehammer tactic though -- it just evaluates numeric expressions, and its aim is to generate efficient proofs of the correctness of the evaluation.
Kyle Miller (Dec 10 2023 at 17:53):
There are cases where norm_num
is faster than rfl
because it knows a better way to compute a function than by appealing to its definition.
Kyle Miller (Dec 10 2023 at 17:58):
(I know that it used to be that because GCD is defined using well-founded recursion that you couldn't really prove anything about it by rfl
, but it seems that examples like example : Nat.gcd 15 25 = 5 := rfl
work now. I'm not sure what the status is for whether that being provable by rfl
is meant to be supported.)
Kyle Miller (Dec 10 2023 at 18:01):
I agree that using linarith
to prove equalities about concrete rationals is overkill.
Kyle Miller (Dec 10 2023 at 18:05):
Eric Rodriguez said:
decide should work regardless of irreducibility I thought
The DecidableEq
instance for Rat
is basically that to prove p = q
then see if (rfl : p.num = q.num)
and (rfl : q.den = q.den)
typecheck. That means whatever is going on in p
and q
have to be reducible enough.
Kyle Miller (Dec 10 2023 at 18:07):
I'm pretty sure that for equalities of naturals, integers, and rationals that decide
has no special powers that rfl
doesn't already have...
Yongyi Chen (Dec 10 2023 at 18:11):
So Rat
derives from DecidableEq
but yet...
example {x : ℚ} : (4 / 2 : ℚ) = 2 := by
/-
failed to reduce to 'true'
Decidable.rec (fun h => (fun x => false) h) (fun h => (fun x => true) h) (instDecidableEqRat (4 / 2) 2)
-/
decide
Yongyi Chen (Dec 10 2023 at 18:14):
This contradicts what the docs say on Decidable
:
If a proposition p is Decidable, then (by decide : p) will prove it by evaluating the decidability instance to isTrue h and returning h.
Kyle Miller (Dec 10 2023 at 18:17):
Right, like I said it depends on checking that rfl
can prove that the numerators and denominators are equal.
Kyle Miller (Dec 10 2023 at 18:18):
This fails:
example {x : ℚ} : (4 / 2 : ℚ).num = (2 : ℚ).num := rfl
so decide
fails.
Kyle Miller (Dec 10 2023 at 18:19):
There's generally no point in using decide
to prove equalities of naturals, integers, and rationals since it's a more roundabout rfl
. I'm pretty sure there are no exceptions here.
Kyle Miller (Dec 10 2023 at 18:20):
Yongyi Chen said:
This contradicts what the docs say on
Decidable
: [...]
There's no contradiction. It failed to evaluate the decidable instance to isTrue
. The word "evaluate" might not be good here though, since "reduce" is would be more precise.
Kyle Miller (Dec 10 2023 at 18:23):
When I hear "evaluate" I think about #eval
. Decidable instances can always be evaluated that way, but they don't generate proofs in that case.
#eval decide ((4 / 2 : ℚ) = 2)
-- true
Yongyi Chen (Dec 10 2023 at 18:32):
So to summarize, for equalities of rational numbers involving literals,
by rfl
, by norm_num
work, while
rfl
( = by exact rfl
), by decide
, and by aesop
don't work,
and everyone but me agrees this is how it should be?
Kyle Miller (Dec 10 2023 at 19:00):
I'd say by rfl
accidentally works, since it's circumventing transparency settings by using the kernel defeq checker.
Kyle Miller (Dec 10 2023 at 19:01):
And this is how it should be in the sense that the designers of Rat made the actual definitions of the arithmetic operations be behind the API.
Yaël Dillies (Dec 10 2023 at 19:02):
Not sure why you would expect aesop
to work here
Yongyi Chen (Dec 10 2023 at 19:02):
Kyle Miller said:
There's no contradiction. It failed to evaluate the decidable instance to
isTrue
. The word "evaluate" might not be good here though, since "reduce" is would be more precise.
OK, I think I thought too naively what Decidable means. So for example there is a term of type Decidable (BB(9001) = 0)
as well (I think?)
Yongyi Chen (Dec 10 2023 at 19:04):
Yaël Dillies said:
Not sure why you would expect
aesop
to work here
To me it seems more surprising a tool like aesop
couldn't do this.
Yaël Dillies (Dec 10 2023 at 19:04):
aesop does logic puzzles. Computing is not a logic puzzle
Kyle Miller (Dec 10 2023 at 19:05):
If reducing Nat.gcd
in the kernel is now reliable (which by my recollection it wasn't before) maybe the arithmetic operations' definitions could be made semireducible rather than irreducible. However, I do not know if rfl
proofs for rationals are actually efficient. I wouldn't be surprised if the norm_num
ones typecheck faster.
Kyle Miller (Dec 10 2023 at 19:06):
@Yaël Dillies Doesn't aesop
use simp
? Couldn't it use norm_num
?
Kyle Miller (Dec 10 2023 at 19:06):
(I actually don't know if aesop
uses simp
or not)
Yongyi Chen (Dec 10 2023 at 19:06):
About norm_num
:
theorem thirds_inefficient : (1 / 3 : ℚ) + 2 / 3 = 1 := by norm_num
expands to
theorem thirds_inefficient : (1 / 3 : ℚ) + 2 / 3 = 1 := of_eq_true
(eq_true
(Mathlib.Meta.NormNum.isNat_eq_true
(Mathlib.Meta.NormNum.IsInt.to_isNat
(Mathlib.Meta.NormNum.IsRat.to_isInt
(Mathlib.Meta.NormNum.isRat_add (Eq.refl HAdd.hAdd)
(Mathlib.Meta.NormNum.isRat_div
(Mathlib.Meta.NormNum.isRat_mul (Eq.refl HMul.hMul)
(Mathlib.Meta.NormNum.IsNat.to_isRat
(Mathlib.Meta.NormNum.isNat_ofNat ℚ (Eq.refl 1)))
(Mathlib.Meta.NormNum.isRat_inv_pos
(Mathlib.Meta.NormNum.IsNat.to_isRat
(Mathlib.Meta.NormNum.isNat_ofNat ℚ (Eq.refl 3))))
(Eq.refl (Int.mul (Int.ofNat 1) (Int.ofNat 1))) (Eq.refl 3)))
(Mathlib.Meta.NormNum.isRat_div
(Mathlib.Meta.NormNum.isRat_mul (Eq.refl HMul.hMul)
(Mathlib.Meta.NormNum.IsNat.to_isRat
(Mathlib.Meta.NormNum.isNat_ofNat ℚ (Eq.refl 2)))
(Mathlib.Meta.NormNum.isRat_inv_pos
(Mathlib.Meta.NormNum.IsNat.to_isRat
(Mathlib.Meta.NormNum.isNat_ofNat ℚ (Eq.refl 3))))
(Eq.refl (Int.mul (Int.ofNat 2) (Int.ofNat 1))) (Eq.refl 3)))
(Eq.refl (Int.ofNat 9)) (Eq.refl 9))))
(Mathlib.Meta.NormNum.isNat_ofNat ℚ (Eq.refl 1))))
with typecheck errors on two of the (Eq.refl 3)
terms complaining of an application type mismatch.
Yaël Dillies (Dec 10 2023 at 19:07):
aesop
does use simp
as a preprocessing step (every time a rule is applied!). I guess norm_num
could be plugged in there too.
Kyle Miller (Dec 10 2023 at 19:08):
- Pretty printing unfortunately fails to round-trip too often. That's the only thing you can infer from
Eq.refl 3
causing typechecking failures. - The proof looks big, but that doesn't mean it's slow to typecheck. These
norm_num
proofs are set up so that defeq checks are trivial, except for places where it's doingNat
computations.
Yaël Dillies (Dec 10 2023 at 19:08):
Yongyi, that doesn't mean much, really. It just means that "pretty-printing doesn't round-trip", namely you can't trust the printing of an expression to elaborate back to that expression.
Yaël Dillies (Dec 10 2023 at 19:09):
It's also pretty big just because every lemma is prefixed with Mathlib.Meta.NormNum
, so :shrug:
Yongyi Chen (Dec 10 2023 at 19:09):
I think, as in the examples at the start of this conversation, there isn't an actual term similar to this that type-checks. Because I think the Eq.refl errors are the same seeing-through-irreducibility issues as earlier.
Kyle Miller (Dec 10 2023 at 19:10):
Yongyi Chen said:
I think the Eq.refl errors are the same seeing-through-irreducibility issues as earlier.
This is false. These are Nat refls.
Yongyi Chen (Dec 10 2023 at 19:11):
Oh, OK. So for the error
argument
Eq.refl 3
has type
3 = 3 : Prop
but is expected to have type
Nat.mul 1 (Nat.succ 2) = Nat.mul 1 ?m.7106 : Prop
what should go instead of Eq.refl 3
?
Kyle Miller (Dec 10 2023 at 19:12):
The problem is that the elaborator isn't able to elaborate the pretty printed term, and it could be because of any number of things, most likely missing implicit arguments.
Kyle Miller (Dec 10 2023 at 19:13):
The pretty printing "sledgehammer" is setting set_option pp.all true
. That one typechecks, I've verified it just now.
Kyle Miller (Dec 10 2023 at 19:13):
But to "what should go instead of Eq.refl 3
", I'd say "nothing, delete that whole proof term and use norm_num
"
Yongyi Chen (Dec 10 2023 at 19:14):
Kyle Miller said:
The pretty printing "sledgehammer" is setting
set_option pp.all true
. That one typechecks, I've verified it just now.
Oh! That's useful. Also verified it on my end.
Yongyi Chen (Dec 10 2023 at 19:15):
Yaël Dillies said:
It's also pretty big just because every lemma is prefixed with
Mathlib.Meta.NormNum
, so :shrug:
With open Mathlib.Meta.NormNum
I guess it's not so bad anymore
theorem thirds_inefficient : (1 / 3 : ℚ) + 2 / 3 = 1 := of_eq_true
(eq_true
(isNat_eq_true
(IsInt.to_isNat
(IsRat.to_isInt
(isRat_add (Eq.refl HAdd.hAdd)
(isRat_div
(isRat_mul (Eq.refl HMul.hMul) (IsNat.to_isRat (isNat_ofNat ℚ (Eq.refl 1)))
(isRat_inv_pos (IsNat.to_isRat (isNat_ofNat ℚ (Eq.refl 3))))
(Eq.refl (Int.mul (Int.ofNat 1) (Int.ofNat 1))) (Eq.refl 3)))
(isRat_div
(isRat_mul (Eq.refl HMul.hMul) (IsNat.to_isRat (isNat_ofNat ℚ (Eq.refl 2)))
(isRat_inv_pos (IsNat.to_isRat (isNat_ofNat ℚ (Eq.refl 3))))
(Eq.refl (Int.mul (Int.ofNat 2) (Int.ofNat 1))) (Eq.refl 3)))
(Eq.refl (Int.ofNat 9)) (Eq.refl 9))))
(isNat_ofNat ℚ (Eq.refl 1))))
which I guess has similar structure to my hand-crafted
theorem thirds : (1 / 3 : ℚ) + 2 / 3 = 1 := div_def 1 _ ▸ div_def 2 _ ▸ inv_def _ ▸ mul_def _ _ ▸ mul_def 2 _ ▸ add_def' _ _ ▸ rfl
Kyle Miller (Dec 10 2023 at 19:17):
And it might elaborate faster than yours, since ▸
is hiding some automation.
Yongyi Chen (Dec 10 2023 at 19:17):
So it does. 35 ms for mine and 20 ms for norm_num
. Wow!
Kyle Miller (Dec 10 2023 at 19:18):
So make sure to profile things before assuming they're inefficient :smile:
Kyle Miller (Dec 10 2023 at 19:20):
(That's still really slow for just adding two rational numbers of course, but adding rationals usually isn't in the hot path for theorem proving)
Yongyi Chen (Dec 10 2023 at 19:21):
True, by rfl
or by eq_refl
is a 5 ms typecheck
Kyle Miller (Dec 10 2023 at 19:26):
For larger examples, the difference between by rfl
and by norm_num
goes down.
-- 0.0138 s
example : (1/10001 + 1/10002 + 1/10003 + 1/10004 + 1/10005 : ℚ) = 2781112528027793/5563893612361263340 := by rfl
-- 0.0185 s
example : (1/10001 + 1/10002 + 1/10003 + 1/10004 + 1/10005 : ℚ) = 2781112528027793/5563893612361263340 := by norm_num
Yongyi Chen (Dec 10 2023 at 19:29):
How long does a sorry
take there?
Kyle Miller (Dec 10 2023 at 19:30):
Too little time for set_option trace.profiler true
to report it
Yongyi Chen (Dec 10 2023 at 19:31):
Oh very interesting! sorry
there takes over 100 ms to elaborate on my Windows machine. Let me dual-boot and see if it's a Windows issue.
Kyle Miller (Dec 10 2023 at 19:32):
There's a lot of noise in these measurements, so my method is to set up the three examples
and then insert some whitespace before the first example
to get them to all re-elaborate
Kyle Miller (Dec 10 2023 at 19:33):
When I first created the sorry
example it was measureable on its own (at 300ms or so), but then after that it stopped being measurable.
Kyle Miller (Dec 10 2023 at 19:34):
Maybe it's something with the process of typing out sorry
putting a lot of pressure on the elaborator due to repeated re-elaboration on each keystroke?
Yongyi Chen (Dec 10 2023 at 19:38):
Haha it was something stupid on my part. The section I was working in had variables for modules and other high-complexity objects. Going into a fresh file and using #time shows a timing of around 13ms for sorrys
Kyle Miller (Dec 10 2023 at 19:41):
Oh, yeah, variable
bindings get re-elaborated for every declaration from scratch
Yongyi Chen (Dec 10 2023 at 19:41):
Results: 13 ms for sorry
, 20 ms for rfl
, and 29 ms for norm_num
Last updated: Dec 20 2023 at 11:08 UTC