Zulip Chat Archive
Stream: lean4
Topic: Proving sizeOf lt for Int is the same as Int lt
Anders Christiansen Sørby (Dec 28 2023 at 13:57):
How can I prove this to create this range
theorem Int.sizeOf_lt: ∀ a b : Int, a < b → sizeOf a < sizeOf b := by eq_refl
def Int.range (start stop : Int) : List Int :=
if h : start < stop then
have : sizeOf (stop - (start + 1)) < sizeOf (stop - start) := by {
apply Int.sizeOf_lt
rw [
Int.sub_eq_add_neg, Int.neg_add, ←Int.add_assoc,
Int.sub_eq_add_neg, ←Int.sub_eq_add_neg, ←Int.sub_eq_add_neg
]
apply Int.sub_lt_self (stop-start)
apply Int.zero_lt_one
}
start :: range (start + 1) stop
else
[]
termination_by range start stop => stop - start
Anders Christiansen Sørby (Dec 28 2023 at 14:17):
Isn't Int.sizeOf_lt pretty obvious?
James Gallicchio (Dec 29 2023 at 01:04):
unfortunately it is untrue:
theorem Int.sizeOf_lt: ∀ a b : Int, a < b → sizeOf a < sizeOf b := sorry
example : False := by
have := Int.sizeOf_lt (-2) 1 (by decide)
revert this; rw [imp_false, Nat.not_lt]
decide
James Gallicchio (Dec 29 2023 at 01:06):
Maybe try Int.natAbs (stop - start)
as the measure? There should be lemmas for then manipulating such terms correctly. The Int sizeOf
instance seems to not be intended for general use.
James Gallicchio (Dec 29 2023 at 01:10):
(This seems like an easy footgun for beginners... @Mario Carneiro would it be reasonable to add a WF instance to Std with invImage Int.natAbs
as the measure? I think that is what most people would expect the behavior to be here)
Mario Carneiro (Dec 29 2023 at 01:10):
I think those are the same?
James Gallicchio (Dec 29 2023 at 01:11):
I believe the sizeOf instance adds 1 for negations.
Mario Carneiro (Dec 29 2023 at 01:11):
it might add 1 in both cases but otherwise it should be the same
James Gallicchio (Dec 29 2023 at 01:12):
example : sizeOf (1 : Int) = 2 := by decide
example : sizeOf (-1 : Int) = 1 := by decide
James Gallicchio (Dec 29 2023 at 01:12):
:face_with_raised_eyebrow: which is NOT what I was expecting
James Gallicchio (Dec 29 2023 at 01:12):
(also the Int sizeOf has ~no lemmas in mathlib or std, whereas natAbs has many)
Mario Carneiro (Dec 29 2023 at 01:13):
oh right of course, -1
is negSucc 0
so it has size 0 + 1
James Gallicchio (Dec 29 2023 at 01:14):
I keep forgetting we don't use one of the fancy Z encodings from HoTT >_>
Mario Carneiro (Dec 29 2023 at 01:14):
I think it would be best not to try to guess a measure here
Mario Carneiro (Dec 29 2023 at 01:14):
The example above actually wants toNat (stop - start)
not natAbs (stop - start)
as the measure
Mario Carneiro (Dec 29 2023 at 01:15):
which is the same as subNatNat stop start
Henrik Böving (Dec 29 2023 at 01:16):
James Gallicchio said:
unfortunately it is untrue:
theorem Int.sizeOf_lt: ∀ a b : Int, a < b → sizeOf a < sizeOf b := sorry example : False := by have := Int.sizeOf_lt (-2) 1 (by decide) revert this; rw [imp_false, Nat.not_lt] decide
could slimcheck have falsified this?
Mario Carneiro (Dec 29 2023 at 01:17):
sizeOf
is not computable so maybe not
James Gallicchio (Dec 29 2023 at 01:19):
My gut says either toNat
or natAbs
would be better than the status quo with it defaulting to sizeOf. Either toNat
or natAbs
work in Anders' case since we have start < stop
, and most use cases are going to be in the same boat.
Mario Carneiro (Dec 29 2023 at 01:19):
import Mathlib.Util.CompileInductive
import Mathlib.Tactic.SlimCheck
compile_inductive% Int
theorem Int.sizeOf_lt: ∀ a b : Int, a < b → sizeOf a < sizeOf b := by slim_check
-- ===================
-- Found problems!
-- a := -1
-- b := 0
-- guard: -1 < 0
-- issue: 1 < 1 does not hold
-- (0 shrinks)
-- -------------------
Henrik Böving (Dec 29 2023 at 01:19):
huzzah
Mario Carneiro (Dec 29 2023 at 01:22):
James Gallicchio said:
My gut says either
toNat
ornatAbs
would be better than the status quo with it defaulting to sizeOf. EithertoNat
ornatAbs
work in Anders' case since we havestart < stop
, and most use cases are going to be in the same boat.
No defaulted sizeOf would help here, because the definition is not decreasing on start
or stop
or (start, stop)
Mario Carneiro (Dec 29 2023 at 01:22):
so you need to give a termination_by
anyway, and in that case you can choose to use a useful measure
Mario Carneiro (Dec 29 2023 at 01:23):
The builtin decreasing tactics are structured to work well on the compiler-generated sizeOf
. If that doesn't work, then just specify your own termination measure and ignore sizeOf
entirely
James Gallicchio (Dec 29 2023 at 01:35):
I guess my discomfort is because anders' code has a termination_by
measure, but the sizeOf
was still inserted into that measure because of the default WellFoundedRelation
:
instance (priority := low) [SizeOf α] : WellFoundedRelation α :=
sizeOfWFRel
which seems like an easy footgun to overlook here.
Mario Carneiro (Dec 29 2023 at 01:38):
I think anders' code should be treated as incorrect, the integers are not well founded
Mario Carneiro (Dec 29 2023 at 01:42):
it's just unfortunate that because termination checking in lean is a ball of heuristics we can't just come out and say that, and instead we give some misleading hints relating to the compiler-internal sizeOf
function
James Gallicchio (Dec 29 2023 at 01:42):
Is that default WFR instance used extensively?
James Gallicchio (Dec 29 2023 at 01:43):
I'm wondering if maybe it leads to more confusion than it is worth..??
Mario Carneiro (Dec 29 2023 at 01:43):
well of course, it is used by termination proofs when you don't use termination_by
at all
James Gallicchio (Dec 29 2023 at 01:44):
hm. unfortunate.
Mario Carneiro (Dec 29 2023 at 01:45):
removing that instance (and the default SizeOf
instance that sets everything to 1
) would mean that not finding an instance is a new failure mode of definitions, which is something we would have to explain and I'm not sure that's better than the status quo
Anders Christiansen Sørby (Dec 30 2023 at 20:45):
How do I show 0 < stop -start
when I already have start < stop
? And why is showing something so trivial so complex? Shouldn't there be tactics and lemmas that solve these pretty easily?
open Int in
def Int.range (start stop : Int) : Array Int :=
if h : start < stop then
have : natAbs (stop - (start + 1)) < natAbs (stop - start) := by {
rw [
Int.sub_eq_add_neg, Int.neg_add, ←Int.add_assoc,
Int.sub_eq_add_neg, ←Int.sub_eq_add_neg, ←Int.sub_eq_add_neg
]
have h3 : 0 < stop - start := by apply ←Int.lt_of_sub_pos stop start;apply h
generalize stop - start = a
apply ←h -- here I want to be able to replace 0 <= a -> natAbs a = a
apply Int.natAbs_lt_natAbs_of_nonneg_of_lt
apply Int.sub_lt_self a
apply Int.zero_lt_one
}
#[start] ++ (range (start + 1) stop)
else
#[]
termination_by range start stop => natAbs (stop - start)
Mario Carneiro (Dec 30 2023 at 20:48):
Mario Carneiro (Dec 30 2023 at 20:48):
if you are using mathlib, then yes there are things like linarith
that will take care of problems like this
Anders Christiansen Sørby (Dec 30 2023 at 21:14):
I'm not using mathlib currently. Just Std. I'm trying to make a game.
Mario Carneiro (Dec 30 2023 at 21:17):
Here's a simpler (and faster) implementation which avoids the need for a messy termination proof:
def Int.range (start stop : Int) : Array Int :=
let rec go
| 0, _, arr => arr
| n+1, start, arr => go n (start + 1) (arr.push start)
let n := (stop - start).toNat
go n start (Array.mkEmpty n)
Anders Christiansen Sørby (Dec 30 2023 at 21:17):
Thanks
Kim Morrison (Jan 03 2024 at 23:43):
@Anders Christiansen Sørby these sorts of things work out of the box via omega
:
import Std.Tactic.Omega.Frontend
example (x y : Int) (h : x < y) : 0 < y - x := by omega
example (x y : Int) (h : x < y) : Int.natAbs (y - (x + 1)) < Int.natAbs (y - x) := by omega
Last updated: May 02 2025 at 03:31 UTC