congruence
sometimes unfolds definitions and sometimes specializes hypotheses. I want to understand when (and/or whether this could be improved upon). Consider the following example, where (1) is not coherent with (3) and (2) is not coherent with (4). Bug or intention?
Definition twice (f: nat -> nat) x := f (f x).
Lemma twice_def f x : twice f x = f (f x).
Proof. reflexivity. Qed.
Definition twice_spec f := forall x, twice f x = f (f x).
Goal forall f x, twice f x = f (f x).
Proof.
intros f x.
Fail congruence. (* (1) somewhat expected; still curious since https://github.com/coq/coq/issues/13778 *)
pose proof (H := twice_def).
Fail congruence. (* (2) unexpected, H could be specialized with f, x *)
assert (H' : twice_spec f) by exact (H f).
Fail Fail congruence. (* (3) unexpected, H' needs unfolding; why different from (1)? *)
clear H'.
specialize (H f).
congruence. (* (4) somewhat expected, H is instantiated with x; why different from (2)? *)
Qed.
I did some digging in the code and found the answer: congruence
is not able to specialize variables that occur as App
heads.
In the above, in f (f x)
the variable f
cannot be instantiated. Compare with the following example
Axiom p : (nat -> nat) -> nat.
Goal (forall f, p f = f 0) -> forall f, p f = f 0.
Proof. Fail congruence. Admitted.
Definition ap {X Y : Type} (f : X -> Y) (x : X) := f x.
Goal (forall f, p f = ap f 0) -> forall f, p f = ap f 0.
Proof. congruence. Qed.
The first goal fails because it contains f 0
where f
needs to be instantiated.
The second goal succeeds because in ap f 0
the variable f
is not applied to an argument.
I'm not sure whether congruence
can be improved to handle the general case.
Implements the standard Nelson and Oppen congruence closure algorithm ...
From having a quick look at the paper of Nelson and Oppen, I think the abstract algorithm should be able to deal with this. But probably it's the implementation that doesn't recognize that (forall f, p f = f 0)
describes an equality that it can use.
That seems a bit doubtful. Nelson-Oppen is a first-order algorithm. It should not even notice p f
.
From what I understand looking at the code, it implements a rudimentary matching procedure for which it is faster/simpler knowing the exact head of a (nested) application. It would require a larger overhaul (and I suspect that it is indeed possible) of the internal congruence
code to accommodate for the described failing examples. Also for performance reasons, using Coqs unification for this could be too powerful/slow.
I am now experimenting with an extension PR#14650 of congruence
that covers the failing example.
Last updated: Sep 23 2023 at 08:01 UTC