-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unification structure #59
base: master
Are you sure you want to change the base?
Conversation
A reflexive unification problem induces constraints on the possible definitions of `?e`: once `?e` is defined, it may generate new unification problems between terms of the instances that contain existential variables. | ||
|
||
A reflexive unification problem can be simplified as follows: | ||
- Variables of the context whose instances in the substitution are provably incompatible (e.g. because corresponding to distinct head normal forms) can be removed from the context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This optimization seems unsound?
Consider x : nat |- ?e : nat
and the pair of unification problems |- ?e@{x:=O} == ?e@{x:=S O}
and x : nat |- ?e@{x:=x} == match x with O => O | S _ => O end
. The only valid solution of this is ?e := match x with O => O | S _ => O end
, which refers to x
.
And in fact Coq fails to solve such a problem:
Check let e := (fun x => ?[e]) in
eq_refl : (e O, e) = (e (S O), fun x => match x with O => O | S _ => O end).
(* Error:
In environment
e := fun _ : nat => ?e : nat -> nat
The term "eq_refl" has type "(e 0, e) = (e 0, e)" while it is expected to have type
"(e 0, e) = (e 1, fun x : nat => match x with
| 0 | _ => 0
end)" (cannot instantiate "?e" because "x" is not in its scope).
*)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reported as coq/coq#15110
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, this should be distinct neutral, i.e. blocking on a variable, head normal forms. Current code is indeed wrong and should be modified.
|
||
A reflexive unification problem can be simplified as follows: | ||
- Variables of the context whose instances in the substitution are provably incompatible (e.g. because corresponding to distinct head normal forms) can be removed from the context. | ||
- If both instances are free of existential variables, the problem can be dropped since its resolution will eventually only create equations between convertible existential-variables-free terms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand this one. Is conversion run again after unification completes? How do you get convertibility constraints? Also, this seems overly optimistic, consider x : nat |- ?e : nat
and |- ?f : nat
and the pair of unification problems |- ?e@{x:=O} == ?e@{x:=S O}
and x : nat |- ?e@{x:=x} == match x with O => O | S _ => ?f end
. We must instantiate ?e
with match x with O => O | S _ => ?f end
, and only after this substitution do we get |- O == ?f
out of the first unification problem, which it seems would have been dropped according to this rule.
It seems that Coq can solve this problem, though?
Check let e := (fun x => ?[e]) in
let f := ?[f] in
eq_refl : (e, e O) = (fun x => match x with O => O | S _ => f end, e (S O)).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems I meant "If both instances are pointwise existential-variable-free compatible terms". So, with your previous remark, it should be "If both instances are pointwise existential-variable-free compatible neutral terms".
Your example works because e := fun x => match x with O => O
is solved first. If you invert the order of unification problems:
Check let e := (fun x => ?[e]) in
let f := ?[f] in
eq_refl : (e O, e) = (e (S O), fun x => match x with O => O | S _ => f end).
it fails.
## Simplification of atomic unification problems | ||
|
||
An atomic unification problem can be simplified as follows: | ||
- Variables of the context of the existential variables that are bound to terms mentioning variables of the context of the equations in non-erasable position in only one of the two instances can be restricted since there would be no way to make these terms match a subterm of the instance of the other existential variables; otherwise said, both existential variables can be restricted on the (well-typed closure of the) subsets of their context which intersect. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "non-erasable position" mean? What does "restricted" mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm understanding correctly, this again seems incorrect for the same reason as above. We might have consider x : nat |- ?e : bool
and x : nat |- ?e' : bool
and the triple of unification problems x, y : nat |- ?e@{x:=S x} == ?e'@{x:=S y}
and x : nat |- ?e@{x:=x} == match x with O => true | S _ => false end
and x : nat |- ?e@{x:=x} == match x with O => true | S _ => false end
. We must instantiate ?e
and e'
both with match x with O => true | S _ => false end
.
Check let e := (fun x => ?[e]) in
let e' := (fun x => ?[e']) in
fun x y => eq_refl
: (e (S x), e, e') =
(e' (S y), (fun x => match x with O => true | S _ => false end),
(fun x => match x with O => true | S _ => false end)).
(* Error:
In environment
e := fun x : nat => match x with
| 0 => true
| S _ => false
end : nat -> bool
e' := fun _ : nat => false : nat -> bool
x : nat
y : nat
The term "eq_refl" has type "(e (S x), e, e') = (e (S x), e, e')" while it is expected to have type
"(e (S x), e, e') =
(e' (S y), fun x : nat => match x with
| 0 => true
| S _ => false
end, fun x : nat => match x with
| 0 => true
| S _ => false
end)" (cannot unify "e'" and
"fun x : nat => match x with
| 0 => true
| S _ => false
end").
*)
*Higher order pattern unification* (see Miller 1991 and Pfenning 1991) is a special case of simple unification problem when the substitution is made only of distinct variables. In this case, there is a single solution obtained by projecting the unique possible variable or imitating if not a variable. | ||
|
||
Pattern unification has various refinements (see e.g. Libal and Miller 2016) such as: | ||
- expecting distinct variables only among the variables that actually occur in the term to match |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this can cause the pattern solution to be incorrect if there are other variables in the context which are assigned to terms which are headed by a constructor, because we may choose whether or not to match on them. (Consider x, y : bool |- ?e
and x : bool |- ?e@{x:=x,y:=true} == x
, we can solve this with either x
or with if y then x else _
, and the latter may be manded by a future unification problem.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be formulated clearly. It is among the variables
, so the presence of true
makes that it is not a pattern (see the Option.List.map
in Evarsolve.is_unification_pattern_evar
; the restriction to actual dependencies in get_actual_deps
comes after).
|
||
Pattern unification has various refinements (see e.g. Libal and Miller 2016) such as: | ||
- expecting distinct variables only among the variables that actually occur in the term to match | ||
- identifying arguments that are irrefutable patterns over variables at their leaves (e.g. `(x,(y,z))` with those variables (i.e. a form of curryfication) (see PR [#14798](https://github.com/coq/coq/pull/14798)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only guaranteed to generate a good solution either (a) in the presence of eta conversion for the record type, or (b) when no subterm of the constructor tree, other than the leaves, occurs in the RHS. e.g., x, y, z |- ?e@{x:=(x,(y,z))} == (y,z)
can be solved with either snd x
or (fst (snd x), snd (snd x))
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
- Second-order unification heuristics: e.g. to resolve `?P t =?= u` by selecting a number of occurrences of subterms unifiable to `t` in `u` (see `Evarconv.second_order_matching`). Such a heuristic is central to any form of reasoning by induction. As far as I can recap, it is currently used in Coq only for tactics. | ||
- One issue is to recognize subterms: we can assume `t` free of existential variables and reason modulo convertibility, or we can reason using syntactic equality, or syntactic equality discarding non-essential, i.e. reinferable arguments (i.e., morally the implicit arguments). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be subsumed by introducing constructors of types with eta. That is, if we have ?P t =?= u
, we can always instantiate ?P
with fun x => ?Q
, turning the application into a substitution. And if we have fst ?x =?= u
(or match ?x with pair x y => Q end =?= u
) and primitive projections / eta are turned on, it should always be safe to instantiate ?x
with (?a, ?b)
(though we probably want to run canonical structures first, and we maybe want to make sure that u
doesn't reduce to something that structurally matches such a projection/match, because otherwise we may forget information that we want to remember for first-order heuristics)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see this is mentioned below
|
||
- Second-order unification heuristics: e.g. to resolve `?P t =?= u` by selecting a number of occurrences of subterms unifiable to `t` in `u` (see `Evarconv.second_order_matching`). Such a heuristic is central to any form of reasoning by induction. As far as I can recap, it is currently used in Coq only for tactics. | ||
- One issue is to recognize subterms: we can assume `t` free of existential variables and reason modulo convertibility, or we can reason using syntactic equality, or syntactic equality discarding non-essential, i.e. reinferable arguments (i.e., morally the implicit arguments). | ||
- Another issue is that typing constraints induce equivalence classes of subterms that have to be abstracted altogether. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean? Or what's an example?
- Systematically eta-reduce the solutions to existential variables (with all kinds of eta). On one side, this seems to be the natural expectation. On the other side, this would avoid caring about whether it might be dommageable to eta-expand this or that solution in the unification algorithm. | ||
- Give an order between constants which would allow to predict which side to reduce in `E[c] =?= E'[c]`. | ||
- Keep constants unfolding to fixpoints folded so as to concentrate on the essential and reduce the size of expressions, including in evaluation contexts (i.e. stacks). | ||
- Do more aggressive restrictions of contexts (e.g. Sozeau and Ziliani seem to take the downwards well-typed closure of restrictions while Coq takes the upwards well-typed closure - knowing that taking the closure by dependency requires in theory to take care of erasable occurrences)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between downwards and upwards here? (Or what's an example?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By downwards
I mean the largest well-formed context which exclude the forbidden variables. By upwards
, I mean the smallest well-formed context which include the allowed variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, it seems that upwards is more powerful unless we have reduced enough that no further occurrences of forbidden variables can be erased by reduction. Consider T : Type, f : T -> Type, x : T |- ?e : Type
, and the pair of problems A, B : Type, f : bool -> Type, x : bool |- ?e@{T:=(fun _ => bool) A,f:=f,x:=x} == ?e@{T:=(fun _ => bool) B,f:=f,x:=x}
and f : bool -> Type, x : bool |- ?e@{T:=bool,f:=f,x:=x} == f x
. IIUC, upwards closure would keep T
, while downwards would remove it? (Do you have an example not involving reduction where they differ where upwards closure does better than downwards closure?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But maybe this is what you meant by "erasable"?
Co-authored-by: Jason Gross <[email protected]>
Co-authored-by: Jason Gross <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the review! I'll answer the remaining questions when I'll have more time.
A reflexive unification problem induces constraints on the possible definitions of `?e`: once `?e` is defined, it may generate new unification problems between terms of the instances that contain existential variables. | ||
|
||
A reflexive unification problem can be simplified as follows: | ||
- Variables of the context whose instances in the substitution are provably incompatible (e.g. because corresponding to distinct head normal forms) can be removed from the context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, this should be distinct neutral, i.e. blocking on a variable, head normal forms. Current code is indeed wrong and should be modified.
|
||
A reflexive unification problem can be simplified as follows: | ||
- Variables of the context whose instances in the substitution are provably incompatible (e.g. because corresponding to distinct head normal forms) can be removed from the context. | ||
- If both instances are free of existential variables, the problem can be dropped since its resolution will eventually only create equations between convertible existential-variables-free terms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems I meant "If both instances are pointwise existential-variable-free compatible terms". So, with your previous remark, it should be "If both instances are pointwise existential-variable-free compatible neutral terms".
Your example works because e := fun x => match x with O => O
is solved first. If you invert the order of unification problems:
Check let e := (fun x => ?[e]) in
let f := ?[f] in
eq_refl : (e O, e) = (e (S O), fun x => match x with O => O | S _ => f end).
it fails.
*Higher order pattern unification* (see Miller 1991 and Pfenning 1991) is a special case of simple unification problem when the substitution is made only of distinct variables. In this case, there is a single solution obtained by projecting the unique possible variable or imitating if not a variable. | ||
|
||
Pattern unification has various refinements (see e.g. Libal and Miller 2016) such as: | ||
- expecting distinct variables only among the variables that actually occur in the term to match |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be formulated clearly. It is among the variables
, so the presence of true
makes that it is not a pattern (see the Option.List.map
in Evarsolve.is_unification_pattern_evar
; the restriction to actual dependencies in get_actual_deps
comes after).
|
||
Pattern unification has various refinements (see e.g. Libal and Miller 2016) such as: | ||
- expecting distinct variables only among the variables that actually occur in the term to match | ||
- identifying arguments that are irrefutable patterns over variables at their leaves (e.g. `(x,(y,z))` with those variables (i.e. a form of curryfication) (see PR [#14798](https://github.com/coq/coq/pull/14798)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
- Systematically eta-reduce the solutions to existential variables (with all kinds of eta). On one side, this seems to be the natural expectation. On the other side, this would avoid caring about whether it might be dommageable to eta-expand this or that solution in the unification algorithm. | ||
- Give an order between constants which would allow to predict which side to reduce in `E[c] =?= E'[c]`. | ||
- Keep constants unfolding to fixpoints folded so as to concentrate on the essential and reduce the size of expressions, including in evaluation contexts (i.e. stacks). | ||
- Do more aggressive restrictions of contexts (e.g. Sozeau and Ziliani seem to take the downwards well-typed closure of restrictions while Coq takes the upwards well-typed closure - knowing that taking the closure by dependency requires in theory to take care of erasable occurrences)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By downwards
I mean the largest well-formed context which exclude the forbidden variables. By upwards
, I mean the smallest well-formed context which include the allowed variables.
- Give an order between constants which would allow to predict which side to reduce in `E[c] =?= E'[c]`. | ||
- Keep constants unfolding to fixpoints folded so as to concentrate on the essential and reduce the size of expressions, including in evaluation contexts (i.e. stacks). | ||
- Do more aggressive restrictions of contexts (e.g. Sozeau and Ziliani seem to take the downwards well-typed closure of restrictions while Coq takes the upwards well-typed closure - knowing that taking the closure by dependency requires in theory to take care of erasable occurrences)? | ||
- Filters need to be better understood: why not to directly restrict the context more often? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a performance optimization?
This CEP is about organizing knowledge about the (evarconv) unification algorithm of Coq in the hope to draw directions for the future.
It is a rather extensive first draft but at the same time a rather naive one. A lot should be done to make it even more comprehensive, collecting experiences and contributions of everyone.
Rendered here