Zulip Chat Archive
Stream: lean4
Topic: debugging 'apply' failed to unify
Chris Lovett (Sep 29 2022 at 23:49):
I'm trying to debug why the Eq.subst ha
doesn't work in this proof, but the trace output is not very helpful - or at least I don't know how to make it helpful...
theorem cong_fst_arg {α : Type} (a a' b : α)
(f : α → α → α) (ha : a = a') : f a b = f a' b := by
set_option trace.Meta.isDefEq true in
apply Eq.subst ha
apply Eq.refl
The Eq.subst fails saying:
tactic 'apply' failed, failed to unify
?m.33 a'
with
f a b = f a' b
Is it not able to reach inside the function call and substitute a
for a'
? Because it could do that in lean3, here:
lemma cong_fst_arg {α : Type} (a a' b : α)
(f : α → α → α) (ha : a = a') :
f a b = f a' b :=
begin
apply eq.subst ha,
apply eq.refl
end
Tomas Skrivan (Sep 30 2022 at 07:03):
My guess is that this is a problem with high order unification e.g. filling hole ?m.33 a'
where ?m.33
is a function. This is a hard problem which often does not have a unique solution.
It works, if you specify the motive explicitly:
theorem cong_fst_arg {α : Type} (a a' b : α)
(f : α → α → α) (ha : a = a') : f a b = f a' b := by
apply Eq.subst ha (motive := λ x => f a b = f x b)
apply Eq.refl
Let's try to prove the theorem using this lemma
theorem subst {α : Type} {a a' : α} {f : α → α} (ha : a = a') : f a = f a' := by rw[ha]
The naive attempt fails with a very similar unification problem
example {α : Type} (a a' b : α) (f : α → α → α) (ha : a = a')
: f a b = f a' b := by
-- tactic 'apply' failed, failed to unify ?m.133 a = ?m.133 a' with f a b = f a' b
apply subst ha
It works when you specify f
explicitly: apply subst ha (f := λ x => f x b)
.
I deal with this problem all the time. My usual work around is to state variants of subst
theorem subst_parm1 {α β : Type} {a a' : α} {b : β} {f : α → β → α} (ha : a = a') : f a b = f a' b := by rw[ha]
then the original theorem is solvable by apply subst_parm1 ha
.
I do not really understands the inner workings of high order unification, but a good rule of thumb is that the pattern ?m.33 a'
has to "match syntactically" i.e. all the parts of the function ?m.33
have to be left of a
. Therefore f a b
does not match ?m.33 a'
because the only solution λ x => f x b
is left and right of a
.
It would be great if someone who really understands how this works in Lean would comment on this.
Also any improvement to high order unification would be highly appreciated from my side. For example, this exact problem is making automatic differentiation involving high rank tensors quite difficult.
Chris Lovett (Sep 30 2022 at 20:41):
I have found the set_option trace.Meta.isDefEq true
to be useful, It helped me figure out the required second motive in this case which was tricky (no a' on the right hand side of the proposition).
lemma cong_two_args {α : Type} (a a' b b' : α)
(f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' := by
apply Eq.subst ha (motive := λ x => f a b = f x b')
apply Eq.subst hb (motive := λ x => f a b = f a x)
apply Eq.refl
Chris Lovett (Sep 30 2022 at 21:47):
I wonder if it has something to do with β-conversion (λx => f x) a = f a
? The explanation of the cong_fst_arg
proof in hglv is as follows:
The
Eq.subst
instance we use has?a := a
,?b := a'
, and?p := (λ x => f a b = f x b)
:
lean a = a' → (λx => f a b = f x b) a → (λx => f a b = f x b) a'
In β-reduced form:
lean a = a' → f a b = f a b → f a b = f a' b
The lemma's first assumption matches the hypothesisha : a = a'
, which is passed as an argument in the
first apply invocation. The lemma's second assumption is a trivial equality that can be proved by
applyEq.refl
orrfl
. The lemma's conclusion matches the goal’s target. Notice how a higher-order
variable (e.g.,?p
) can represent an arbitrary context (e.g.,f . . . b
) around a term (e.g.,
a
ora'
). This works becauseapply
unifies up to computation, including β-conversion.
This phrase can represent an arbitrary context (e.g.,
f . . . b) around a term
might be important, does this mean lean3 β-conversion can convert (λx => f x n₁ ... nₖ) a = f a
n₁ ... nₖ and perhaps lean4 cannot? And this is why it could not handle my
f a b function? I could understand in general this is hard, the second motive I have above in
cong_two_args is interesting
(motive := λ x => f a b = f a x)` because it is not converting the first parameter, it is converting the second!, So for lean to find this out it has to kind of try every combination of parameters which could be slow. Perhaps lean4 doesn't want to be slow doing these kinds of expensive searches? Just a wild guess on my part though...
Mario Carneiro (Oct 01 2022 at 02:22):
I believe it is a bug in elabAsElim
. It is leaving some arguments unevaluated before calculating the motive.
Leonardo de Moura (Oct 01 2022 at 02:25):
https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/elabAselim.20issue/near/301653783
Mario Carneiro (Oct 01 2022 at 04:03):
@Leonardo de Moura Sorry for not elaborating, it's not just that. Here's the MWE:
import Lean
open Lean Meta Elab Term
@[elabAsElim] theorem Eq.subst' : type_of% @Eq.subst := @Eq.subst
example {f : α → β} (h : a = a') : f a = f a' := by
refine Eq.subst' h ?_
-- goal is f a = f a', should be f a = f a
I narrowed the issue down to this:
elab "test1" t:term : tactic => do
let e ← elabAppArgs (← mkConstWithFreshMVarLevels ``Eq.subst')
#[] #[.stx t, .stx (← `(?_))] (← Elab.Tactic.getMainTarget) false false
logInfo m!"{← instantiateMVars e}"
elab "test2" t:term : tactic => do
let e ← elabAppArgs (← mkConstWithFreshMVarLevels ``Eq.subst')
#[] #[.expr (← elabTerm t none), .stx (← `(?_))] (← Elab.Tactic.getMainTarget) false false
logInfo m!"{← instantiateMVars e}"
set_option pp.all true
example {f : α → Prop} (h : a = a') : f a' := by
test1 h -- @Eq.subst'.{?u.31628} ?m.31642 (fun (x : ?m.31642) => f a') ?m.31644 ?m.31645 ?m.31646 ?m.31647
test2 h -- @Eq.subst'.{?u.31611} α (fun (x : α) => f x) a a' h ?m.31670
It seems that the elaboration of all the arguments other than the motive and targets are delayed, which makes the kabstract fail to find anything because we need to use h
in this example to determine that the target is a'
. I looked into the lean 3 code and I think it has some auxiliary check of the dependencies between the arguments in the original type to figure out which one is the major premise (which should be elaborated eagerly) and which ones are minor premises (which we should delay until after we have the motive)
Leonardo de Moura (Oct 01 2022 at 14:16):
@Mario Carneiro Thanks for investigating. Let's create an issue with all these notes and analysis. I will take a look after I am done with the compiler. I am assuming this is not blocking anybody. One can use rw
/simp
in tactic mode, and ▸
in term mode.
Last updated: Dec 20 2023 at 11:08 UTC