Zulip Chat Archive
Stream: lean4
Topic: Metavariables preventing unification
Matej Penciak (Mar 22 2025 at 08:29):
I'm running into a puzzling situation. I've been able to minimize it to the following MWE:
inductive HList {α : Type v} (β : α → Type u) : List α → Type (max u v) where
| nil : HList β []
| cons : ∀ {a : α} {as : List α}, β a → HList β as → HList β (a :: as)
@[reducible] def tester (x : HList Fin [1]) : Nat := match x with
| HList.cons _ HList.nil => 0
example : ∃x, tester (HList.cons x HList.nil) = 0 := by
apply Exists.intro
· try rfl -- Doesn't unify
with_unfolding_all rfl -- Does unify
· exact 0
I would expect the first try rfl
to work. It seems like the unresolved metavariable is preventing Lean from seeing through the defeq (if you fill in the metavariable before trying rfl
it works with no problem) though... If that was it, I suppose I'd be satisfied with "this just doesn't work". The weird thing is with_unfolding_all
must be doing something beyond just unfolding definitions, because with_unfolding_all rfl
does work, despite the metavariable still being there!
I've tried to parse through the trace.Meta.isDefEq
traces but I wasn't able to gather much from them (maybe I should be looking at something else?) What exactly is preventing the unification from going through in the first case, and what is with_unfolding_all
doing to make it work in the second?
Sebastian Ullrich (Mar 22 2025 at 12:04):
It's because match
is semireducible
Marcin Kostrzewa (Mar 22 2025 at 13:26):
@Sebastian Ullrich is there any way to control that? For example, the same works for Lists:
def tester2 (x : List Nat): Nat := match x with
| _ :: _ => 0
| _ => 1
example : ∃x, tester2 [x] = 0 := by
apply Exists.intro
· rfl
· use 17
Making the HList matcher reducible would definitely help, given we have examples like this all over our codebase.
Robin Arnez (Mar 22 2025 at 20:48):
defeq doesn't always play nice with metavariables.
try:
example : ∃ x, tester (HList.cons x HList.nil) = 0 := by
refine ⟨?_, ?_⟩
· exact 0
· rfl
Marcin Kostrzewa (Mar 22 2025 at 21:48):
Thanks, but this is just an artificial example distilled down from the actual problem. The actual problem pops up in a custom tactic that uses apply
with user-provided theorems. So to avoid these mvars we'd have to parse the provided theorem and figure out the assignments ourselves – not impossible, but pretty much duplicating the work that apply
should be doing and re-solving the problem that mvars were invented to solve. If we could just get the HList recursor to play nicely that would be awesome. Also not ruling out just bumping the transparency setting for that tactic, but I'm worried about the performance impact of that.
Robin Arnez (Mar 23 2025 at 01:17):
You can try to use goal.apply e { newGoals := .all }
(MVarId.apply) if you want to avoid this problem
Robin Arnez (Mar 23 2025 at 01:19):
(I don't know what exactly your tactic aims to do so I can't tell whether this applies)
Robin Arnez (Mar 23 2025 at 01:20):
With batteries, you can also use fapply
if you want macro instead of elaboration
Marcin Kostrzewa (Mar 23 2025 at 02:48):
So the whole issue happens inside the call to apply
, our problem is not "we ended up with weird mvars", the problem is "apply
creates mvars and then cannot unify the term it created with the goal". We start from an mvar-free goal, try to apply a theorem and apply
fails and deep within the traces of isDefEq
s the actual reason for failure _looks like_ what we've recreated with the apply Exists.intro
above. But the mvar that blocks the unification is not the result of us doing something weird, it's the result of apply
working correctly and then isDefEq
not being able to deal with that.
And the transparency stuff comes in at the apply level – we have apply foo
failing and with_unfolding_all apply foo
succeeding. We've just trimmed it down to this tiny repro to avoid having to copy-paste a bajillion auxiliary definitions. Sorry if I wasn't clear enough with the real nature of this.
Last updated: May 02 2025 at 03:31 UTC