Zulip Chat Archive
Stream: lean4
Topic: Cannot rewrite term being cased on
James Gallicchio (Feb 25 2022 at 21:23):
I've been trying for a while to step through a match expression in a proof, but the rewrite that should be succeeding is not.
Here, I'm trying to rewrite the LazyList.force F
to none
with h'
, so that I can step through the match. rw [h']
fails:
tactic 'rewrite' failed, motive is not type correct
τ : Type ?u.3819
F : LazyList τ
F_len : Nat
R : LazyList τ
R_len : Nat
...
h' : LazyList.force F = none
...
⊢ (match LazyList.force F, (_ : LazyList.force F = LazyList.force F) with
| some (x, F'), h =>
some (x, LBQueue.balance F' (F_len - 1) R R_len (_ : LazyList.length F' = F_len - 1 ∧ LazyList.length R = R_len))
| none, h => none) =
none
Full code is here
(sorry for the not-MWE, I can't figure out how to get this issue to appear elsewhere :sad:)
James Gallicchio (Feb 25 2022 at 21:25):
I can spend some more time trying to find an MWE if the issue is too unintelligible like this
Kevin Buzzard (Feb 25 2022 at 23:42):
Try cases h'
? In lean 3 this would probably make progress, as would subst h'
.
Mario Carneiro (Feb 26 2022 at 10:28):
I believe split
should work in this case. (Lean 4 split
is like split_ifs
from lean 3 but it also works on matches)
James Gallicchio (Feb 26 2022 at 23:03):
Ah, cases h'
seems to reveal the issue:
dependent elimination failed, failed to solve equation
none =
Acc.rec
(fun x₁ ac₁ ih =>
(fun x a =>
PSigma.casesOn (motive := fun x =>
...
Seems like maybe same issue as this topic
James Gallicchio (Mar 04 2022 at 06:18):
Huh, so even after the other topic was resolved, this is still having trouble. Weirdly if I just case on F.force
again (and eliminate one case via contradiction) it works... I just can't directly rewrite into the match, nor split on it
James Gallicchio (Mar 04 2022 at 06:19):
(Doing cases h'
still gives the same dependent elimination failure)
Mario Carneiro (Mar 04 2022 at 06:25):
can you make a MWE?
James Gallicchio (Mar 04 2022 at 06:33):
inductive LazyList (α : Type u)
| nil : LazyList α
| cons (hd : α) (tl : LazyList α) : LazyList α
| delayed (t : Thunk (LazyList α)) : LazyList α
namespace LazyList
def force : LazyList α → Option (α × LazyList α)
| delayed as => force as.get
| nil => none
| cons a as => some (a,as)
end LazyList
def deq (Q : LazyList τ) : Option (τ × LazyList τ) :=
match h:Q.force with
| some (x, F') => some (x, F')
| none => none
theorem deq_correct (Q : LazyList τ)
: deq Q = none ↔ Q.force = none
:= by
cases h':Q.force
case none =>
simp [deq]
cases h'
sorry
James Gallicchio (Mar 04 2022 at 06:34):
I think (?) this shows the right behavior
James Gallicchio (Mar 04 2022 at 06:36):
And this works perfectly fine in replacement of the cases h'
:
match h'':Q.force with
| none => simp
| some _ =>
rw [h'] at h''
contradiction
James Gallicchio (Mar 04 2022 at 06:38):
wait, no, doing that causes a really strange error at the top level declaration deq_correct ???
James Gallicchio (Mar 04 2022 at 06:38):
application type mismatch
deq.match_1 Q fun x h => Option (τ × LazyList τ)
argument has type
(x : Option (τ × LazyList τ)) → x✝ = x → Type u_1
but function has type
(motive : (x : Option (τ × LazyList τ)) → LazyList.force Q = x → Type u_1) →
(x : Option (τ × LazyList τ)) →
(h : LazyList.force Q = x) →
((x : τ) → (F' : LazyList τ) → (h : LazyList.force Q = some (x, F')) → motive (some (x, F')) h) →
((h : LazyList.force Q = none) → motive none h) → motive x h
Leonardo de Moura (Mar 04 2022 at 16:35):
Ideally we would want to write
theorem deq_correct (Q : LazyList τ) : deq Q = none ↔ Q.force = none := by
simp [deq]
cases Q.force <;> simp
or
theorem deq_correct (Q : LazyList τ) : deq Q = none ↔ Q.force = none := by
simp [deq]
split <;> simp
Both cases Q.force
and split
fail when trying to generalize Q.force
. Auxiliary declarations (deq.match_1
and deq.proof_1
) created by Lean are preventing generalize
from creating a type correct term. deq.match_1
is an auxiliary declaration used to represent the match
expression in deq
, and deq.proof_1
is used by Lean when "hiding" all proof terms nested in a definition. We can work around these issues by unfolding these definitions.
theorem deq_correct (Q : LazyList τ) : deq Q = none ↔ Q.force = none := by
simp [deq]
unfold deq.match_1
unfold deq.proof_1
cases Q.force <;> simp
We will try to improve generalize
and how we encode match
expressions to avoid these workarounds in the future.
James Gallicchio (Mar 06 2022 at 19:27):
Trying to apply this workaround in more complicated situations I'm a bit lost again... Here's an MWE that's closer to my actual application:
inductive LazyList (α : Type u)
| nil : LazyList α
| cons (hd : α) (tl : LazyList α) : LazyList α
| delayed (t : Thunk (LazyList α)) : LazyList α
namespace LazyList
def force : LazyList α → Option (α × LazyList α)
| delayed as => force as.get
| nil => none
| cons a as => some (a,as)
def length : LazyList α → Nat
| nil => 0
| cons _ as => length as + 1
| delayed as => length as.get
theorem F_force_some_len_minus_one {L L' : LazyList α}
: L.force = some (a, L') → L'.length = L.length - 1
:= sorry
end LazyList
structure LazyBatchQueue (τ) :=
F : LazyList τ
F_len : Nat
h_lens : F.length = F_len
def deq (Q : LazyBatchQueue τ) : Option (τ × LazyBatchQueue τ) :=
match h:Q.F.force with
| some (x, F') =>
some (x,
⟨F', Q.F_len - 1,
by simp [LazyList.F_force_some_len_minus_one h, Q.h_lens]⟩)
| none => none
theorem deq_correct (Q : LazyBatchQueue τ)
: deq Q = none ↔ Q.F.force = none
:= by
simp [deq]
unfold deq.match_1
unfold deq.proof_1
unfold deq.proof_2
match h':Q.F.force with
| none => simp
| some _ => simp
The biggest difference from the last is that now we are carrying around a proof term with the queue structure (so perhaps that's causing other proof-erasing definitions to be generated?).
The same proof as before (which I think should in principle still work) fails with a reference to deq.proof_2
, so I tried unfolding that also, but now it generates a type incorrect term because it is generalizing Q.F.force
within the proof term from the implementation of deq
...
Is there a consistent way to apply this workaround? I can also just leave sorry
s until the behavior improves, since it isn't urgent.
Mario Carneiro (Mar 06 2022 at 20:09):
My old standby for solving this situation in lean 3 still works in lean 4: Write the generalization motive explicitly. It's not pretty but it works when you have messy dependencies
theorem deq_correct (Q : LazyBatchQueue τ)
: deq Q = none ↔ Q.F.force = none
:= by
simp [deq]
suffices ∀ o h, deq.match_1 Q (fun x h => _) o h _ _ = none ↔ _ from this _ rfl
intros o h
cases o <;> simp [h]
James Gallicchio (Mar 06 2022 at 20:16):
Oh boy, okay -- let me try to parse what that's doing
Mario Carneiro (Mar 06 2022 at 20:20):
the basic idea is that we are simultaneously generalizing Q.F.force
and rfl : Q.F.force = Q.F.force
because the rfl
proof is what prevents us from generalizing Q.F.force
. These two are passed as arguments to deq.match_1
, which already states the generalized type of h : Q.F.force = o
, so if we introduce them at the same time then we don't get any "motive is not type correct" issues
Mario Carneiro (Mar 06 2022 at 20:21):
(It helps to set set_option pp.match false
to see what the actual match application looks like)
James Gallicchio (Apr 02 2022 at 19:06):
Sorry for dragging up an old thread — I’m back to trying to figure this out.
James Gallicchio (Apr 02 2022 at 19:08):
Where are the proof_i
terms used in the match encoding? Even with pp.match
off and pp.all
on they don’t appear anywhere — is it only occurring in the proof of the relevant proposition?
James Gallicchio (Apr 02 2022 at 19:09):
(Trying to understand why those need to be unfolded/what it’s doing when I unfold those)
Leonardo de Moura (Apr 02 2022 at 19:15):
@James Gallicchio We discussed this issue in the dev channel. To provide a better experience for users, we have to change how we encode match
expressions. This is on our TODO list, but it will not happen in the next two months. We have a tutorial at the end of May, and fixing things and adding missing features for it is our current priority. Here are the notes from the dev channel if you are interested.
A user wrote the following function.
def deq (Q : LazyList τ) : Option (τ × LazyList τ) :=
match h:Q.force with
| some (x, F') => some (x, F')
| none => none
The following auxiliary definition is created
@deq.match_1 :
-- Parameters
{τ : Type u_1} →
(Q : LazyList τ) →
-- Motive
(motive : (x : Option (τ × LazyList τ)) → LazyList.force Q = x → Sort u_2) →
-- Discriminants aka major premises
(x : Option (τ × LazyList τ)) →
(h : LazyList.force Q = x) →
-- Alternatives aka minor premises
((x : τ) → (F' : LazyList τ) → (h : LazyList.force Q = some (x, F')) → motive (some (x, F')) h) →
((h : LazyList.force Q = none) → motive none h) →
-- Result type
motive x h
If the goal contains the term deq.match_1 Q <some-motive> (LazyList.force Q) ...
, we cannot generalize LazyList.force Q
.
The auxiliary definition above follows the structure of kernel recursors where the motive does not depend on the major premises (aka discriminants), but it prevents us from generalizing effectively. We could work karound this particular issue by using a different encoding where the motive depends on the discriminants
def deq.match
(τ : Type u)
(d₁ : Option (τ × LazyList τ))
(d₂ : d₁ = d₁)
(motive : (x' : Option (τ × LazyList τ)) → x' = d₁ → Sort v)
(h₁ : (x : τ) → (F' : LazyList τ) → (h : some (x, F') = d₁) → motive (some (x, F')) h)
(h₂ : (h : none = d₁) → motive none h)
: motive d₁ d₂ := by
cases d₁
exact h₂ rfl
exact h₁ _ _ rfl
BTW, I hit similar problems a few times whenever we have match h:t ...
where t
is not a variable. This kind of match
creates a term that cannot be generalized by tactics such as cases
and split
. The issue also affects any match
where the user manually provides a complex motive
by hand + discriminants that are not variables. This second scenario is rare since very few people are willing to write complex motives by hand. The problem with match h:t ...
is that it makes it super easy for users to create a nontrivial motive. :sweat_smile:
This issue is bothering me :sweat_smile: Here is another encoding that fixes the problem, but does not change the order: parameter, motive, discriminants, alternatives.
def deq.match {τ : Type u} (Q : LazyList τ) (motive : (x : Option (τ × LazyList τ)) → Sort v)
(d : Option (τ × LazyList τ))
(h₁ : (x : τ) → (F' : LazyList τ) → (h : d = some (x, F')) → motive (some (x, F')))
(h₂ : (h : d = none) → motive none)
: motive d := by
cases h:d
. exact h₂ h
. exact h₁ _ _ h
It removes rfl
as a discriminant. In retrospect, it is a bit pointless to take an argument that is always set with rfl
.
This fix also avoids the workaround unfold deq.proof_1
.
We would need some extra annotation to make sure we can delaborate this kind of match, but the result will be better.
For example, the deq
function
def deq (Q : LazyList τ) : Option (τ × LazyList τ) :=
match h:Q.force with
| some (x, F') => some (x, F')
| none => none
is currently delaborated as
def deq.{u_1} : {τ : Type u_1} → LazyList τ → Option (τ × LazyList τ) :=
fun {τ} Q =>
match LazyList.force Q, (_ : LazyList.force Q = LazyList.force Q) with
| some (x, F'), h => some (x, F')
| none, h => none
The output is correct, but it exposes our encoding for h:
.
Evgeniy Kuznetsov (Apr 30 2022 at 10:09):
@Leonardo de Moura, in case you decide it's worth your attention:
example {n: Nat}: True :=
match h: n with
| 0 => True.intro
| n + 1 => by
trace_state
exact True.intro
h
has type:
. nightly-2022-04-29
: h : n✝ = n + 1
. nightly-2022-04-30
: h : n✝ = Nat.succ n
Leonardo de Moura (Apr 30 2022 at 16:43):
@Evgeniy Kuznetsov Thanks for the reporting the discrepancy. I changed how the h :
is encoded in to address the issue in this thread and https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/hypothesis.20can't.20be.20used/near/280252919
In the new encoding, h : <discriminant> = <pattern>
. The issues in these threads are not fully resolved, I still have to write a new split
tactic that takes into account the new encoding, and add support in simp
too.
In your example, the discrepancy is on the <pattern>
of h : <discriminant> = <pattern>
, because x+1
is a pseudo-pattern, and only becomes a "real" pattern after we reduce it to Nat.succ x
. The new behavior seems reasonable to me.
I have also noticed discrepancies on the <discriminant>
. In the new encoding, it is exactly the discriminant we typed, and in the previous encoding, the <discriminant>
would be "refined" by previous patterns.
We want to make sure we have the final encoding before we make the first official Lean 4 release this summer.
Leonardo de Moura (Apr 30 2022 at 16:50):
Note that you can still simulate previous encoding by manually providing the "motive" for the match
.
theorem ex {n: Nat}: True :=
match (motive := (n' : Nat) → n = n' → _) n, rfl with
| 0, h => True.intro
| y + 1, h => by
trace_state -- h : n = y + 1
exact True.intro
Leonardo de Moura (Apr 30 2022 at 17:04):
BTW, I will be tweaking with the new encoding in the next few days. So, we may have a few more discrepancies.
Evgeniy Kuznetsov (Apr 30 2022 at 17:12):
Thank you for a comprehensive answer! Just wanted to make sure this is the expected behavior.
Last updated: Dec 20 2023 at 11:08 UTC