Zulip Chat Archive
Stream: lean4
Topic: can this be proved without writing a motive explicitly?
Ted Hwa (May 10 2024 at 03:27):
The lemma below is very simple but seemingly can't be proved without writing out a motive explicity. Simpler approaches result in "motive is not type correct" or similar. It seems like the proof in head is causing the issue.
I want something that will generalize to arbitrary type for state (not just Nat) and arbitrary step function as long as we can prove that it terminates for all states.
import Mathlib.Tactic
def step (state: Nat): Option Nat :=
if state = 0 then none else some (state - 1)
set_option linter.unusedVariables false in
def countdown (state: Nat) :=
match h: step state with
| none => [state]
| some newState => state :: countdown newState
termination_by state
decreasing_by
simp_wf
simp [step] at h
split_ifs at h
simp at h
omega
-- A very simple lemma
lemma initial_state (state: Nat): (countdown state).head (by unfold countdown; split <;> simp) = state := by
unfold countdown
/- ⊢ (match h : step state with
| none => [state]
| some newState => state :: countdown newState).head
⋯ =
state -/
-- The conclusion looks very obvious at this point, but all simple tactics fail:
-- simp -- simp made no progress
-- split -- fails
-- rcases h: step state -- tactic 'generalize' failed, result is not type correct
-- With a naive by_cases approach, the subsequent simp, rw, or simp_rw all fail.
-- rw gives 'motive is not type correct'
-- Instead, I have to write out the motive explicitly.
let motive := fun x => (match h : x with
| none => [state]
| some newState => state :: countdown newState).head (by split <;> simp) = state
by_cases h: step state = none
· have: motive (step state) = motive none := by rw [h]
simp [motive] at this
exact this
· sorry -- this case is similar after converting h to step state = some newState
Kim Morrison (May 10 2024 at 04:53):
How about
import Mathlib.Tactic
def step (state: Nat): Option Nat :=
if state = 0 then none else some (state - 1)
set_option linter.unusedVariables false in
def countdown (state: Nat) :=
match h: step state with
| none => [state]
| some newState => state :: countdown newState
termination_by state
decreasing_by sorry
lemma initial_state (state: Nat): (countdown state).head (by unfold countdown; split <;> simp) = state := by
unfold countdown
generalize_proofs p
split at p <;> rfl
Kim Morrison (May 10 2024 at 04:54):
(Clue: all the incorrect motives must have been about the proof inside .head. So use generalize_proofs to get access to it directly.)
Ted Hwa (May 10 2024 at 06:49):
Thank you, that was really helpful in eliminating motives from several of my proofs. However, there are still some others which I couldn't eliminate. In the more difficult cases, the motive has to include an equality hypothesis because the statement to be proved would be false for an arbitrary x. Then I had to fall back on Eq.rec to use the motive. I don't have a minimized example yet.
Kim Morrison (May 10 2024 at 06:53):
Looking forward to seeing them! :-)
Ted Hwa (May 10 2024 at 07:06):
Ok, here's a reasonably minimized example.
import Mathlib.Tactic
def step (state: Nat): Option Nat :=
if state = 0 then none else some (state - 1)
set_option linter.unusedVariables false in
def countdown (state: Nat) :=
match h: step state with
| none => [state]
| some newState => state :: countdown newState
termination_by state
decreasing_by sorry
lemma tailLast (state: Nat):
∀ s ∈ countdown state,
List.getLast (countdown state) (by sorry) =
List.getLast (countdown s) (by sorry) := by sorry
lemma suffixEqualsCountdownOfHead {suffix: List Nat}
(suffix_nonempty: suffix ≠ []) (state: Nat):
suffix ∈ (countdown state).tails →
suffix = countdown (suffix.head suffix_nonempty) := by sorry
lemma tailLastSuffix (state: Nat) (suffix: List Nat)
(suffix_nonempty: suffix ≠ []) (suffix_in_tails: suffix ∈ (countdown state).tails):
List.getLast (countdown state) (by sorry) =
List.getLast suffix suffix_nonempty := by
have foo := tailLast state (suffix.head suffix_nonempty)
have foo2 := suffixEqualsCountdownOfHead suffix_nonempty state suffix_in_tails
-- I want to do rw [foo2]. Here the equality hypothesis is needed in the motive
-- because a general x could be empty, then no proof is possible for x.getLast
let motive := fun x (h2: countdown (suffix.head suffix_nonempty) = x) =>
List.getLast (countdown state) (by sorry) =
x.getLast (by rw [← h2]; unfold countdown; split <;> simp)
have := @Eq.rec (motive := motive) (by
simp [motive]
apply foo
sorry -- rest of the proof is not relevant to this issue
) suffix foo2.symm
exact this
Ted Hwa (May 10 2024 at 07:13):
Above code has been edited slightly to include the proof for x.getLast since that's important to the issue.
Ted Hwa (May 11 2024 at 04:24):
In my last example, if I do simp_rw [foo2] (on the line with the comment I want to do rw [foo2]), I get
tactic 'simp' failed, nested error:
maximum recursion depth has been reached
and after turning on diagnostics as suggested, I get the following output which I don't know how to interpret:
[simp] used theorems (max: 99, num: 1): ▼
_uniq.680 ↦ 99
[simp] tried theorems (max: 99, num: 1): ▼
_uniq.680 ↦ 99, succeeded: 99
[reduction] unfolded reducible declarations (max: 32, num: 1): ▼
Ne ↦ 32
Ted Hwa (May 11 2024 at 22:58):
I found a solution. The following code replaces the code starting with the motive line. But I don't really understand why rw or simp_rw sometimes works and sometimes doesn't in these situations.
rw [← foo2] at foo
apply foo
sorry -- rest of proof is same as the last `sorry` before
Ted Hwa (Jun 06 2024 at 06:21):
The initial generalize_proofs solution doesn't seem to work on the latest nightly build (mathlib nightly-testing-2024-06-05, lean nightly-2024-06-05). It works on 4.8.0.
import Mathlib.Tactic.GeneralizeProofs
def step (state: Nat): Option Nat :=
if state = 0 then none else some (state - 1)
set_option linter.unusedVariables false in
def countdown (state: Nat) :=
match h: step state with
| none => [state]
| some newState => state :: countdown newState
termination_by state
decreasing_by sorry
theorem initial_state (state: Nat): (countdown state).head (by unfold countdown; split <;> simp) = state := by
unfold countdown
generalize_proofs p
split at p <;> rfl -- works on 4.8.0 but on latest nightly, gives tactic 'splitMatch' failed, nested error: 'applyMatchSplitter' failed, failed to generalize target
Ted Hwa (Jun 07 2024 at 05:17):
Filed a bug at https://github.com/leanprover/lean4/issues/4390
Last updated: May 02 2025 at 03:31 UTC