An intuitive look at setoid rewriting
The use case I'm familiar with
When working with a deep embedding of a logic in Coq, you will probably end up needing to face the beast that is setoid rewriting. The use case for this is rewriting terms using your entailment relation modulo some interpretation, which bridges the semantics of your logic and the logic of Coq.
Here's an example.
From Coq Require Import ZArith.
From Coq Require Import Setoid Morphisms Program.Basics.
Inductive f :=
| And : f -> f -> f
| Or : f -> f -> f
| EqI : Z -> f.
Inductive satisfies : f -> Z -> Prop :=
| iand : forall f1 f2 m,
satisfies f1 m ->
satisfies f2 m ->
satisfies (And f1 f2) m
| ior_l : forall f1 f2 m,
satisfies f1 m ->
satisfies (Or f1 f2) m
| ior_r : forall f1 f2 m,
satisfies f2 m ->
satisfies (Or f1 f2) m
| i : forall i,
satisfies (EqI i) i.
Definition entails f1 f2 : Prop :=
forall m, satisfies f1 m -> satisfies f2 m.
Lemma and_comm : forall f1 f2, entails (And f1 f2) (And f2 f1).
Admitted.
Lemma or_comm : forall f1 f2, entails (Or f1 f2) (Or f2 f1).
Admitted.
#[global]
Instance Proper_satisfies : Proper (entails ==> eq ==> impl) satisfies.
Proof.
unfold entails, Proper, respectful, impl.
intros; subst; eauto.
Qed.
#[global]
Instance Proper_Or : Proper (entails ==> entails ==> entails) Or.
Proof.
unfold Proper, entails, respectful.
intros x y H1 a b H2 z H3. inversion H3.
- apply ior_l. apply H1. auto.
- apply ior_r. apply H2. auto.
Qed.
#[global]
Instance Reflexive_entails : Reflexive entails.
Proof.
unfold Reflexive, entails. auto.
Qed.
(* note that the two branches of And need to be distinct for `and_comm` to work *)
Goal satisfies (Or (EqI 1) (And (EqI 1) (EqI 2))) 1.
Proof.
rewrite <- or_comm. (* Uses Proper_satisfies *)
rewrite <- and_comm. (* Uses Proper_satisfies, Proper_Or, and Reflexive_entails *)
Setoids
What is a setoid? Other people have explained it better, but it's a type with an equivalence1 relation.
TODO li yao post, penn pl club
This relation can be used to rewrite terms deep in some context, provided the relation is proper with respect to the context. This is easier.
This is allowed because.
To enable rewriting, you will have to provide an instance of the Proper
typeclass.
Proper to show that your relation is a congruence How to read
Instances
An instance is needed for every enclosing constructor. For example,
example
This works even if conditional.
Conditional rewriting
From Coq Require Import ZArith.
From Coq Require Import Setoid Morphisms Program.Basics.
Inductive f :=
| And : f -> f -> f
| Or : f -> f -> f
| EqI : Z -> f.
Inductive satisfies : f -> Z -> Prop :=
| iand : forall f1 f2 m,
satisfies f1 m ->
satisfies f2 m ->
satisfies (And f1 f2) m
| ior_l : forall f1 f2 m,
satisfies f1 m ->
satisfies (Or f1 f2) m
| ior_r : forall f1 f2 m,
satisfies f2 m ->
satisfies (Or f1 f2) m
| i : forall i,
satisfies (EqI i) i.
Definition entails f1 f2 : Prop :=
forall m, satisfies f1 m -> satisfies f2 m.
Lemma and_comm : forall f1 f2, entails (And f1 f2) (And f2 f1).
Admitted.
Lemma or_comm : forall f1 f2, entails (Or f1 f2) (Or f2 f1).
Admitted.
#[global]
Instance Proper_satisfies : Proper (entails ==> eq ==> impl) satisfies.
Proof.
unfold entails, Proper, respectful, impl.
intros; subst; eauto.
Qed.
#[global]
Instance Proper_Or : Proper (entails ==> entails ==> entails) Or.
Proof.
unfold Proper, entails, respectful.
intros x y H1 a b H2 z H3. inversion H3.
- apply ior_l. apply H1. auto.
- apply ior_r. apply H2. auto.
Qed.
#[global]
Instance Reflexive_entails : Reflexive entails.
Proof.
unfold Reflexive, entails. auto.
Qed.
(* note that the two branches of And need to be distinct for `and_comm` to work *)
Goal satisfies (Or (EqI 1) (And (EqI 1) (EqI 2))) 1.
Proof.
rewrite <- or_comm. (* Uses Proper_satisfies *)
rewrite <- and_comm. (* Uses Proper_satisfies, Proper_Or, and Reflexive_entails *)
Errors and debugging
While futzing around defining proper instances, you will see the dreaded failed to understand
This is a known issue. Unfortunately it's been a known issue for 6 years with no resolution https://github.com/coq/coq/issues/6141
the typeclass errors can get really large, leaving you with no choice other than to bisect your code until you find the error. https://github.com/coq/coq/issues/13942
Heuristics for understanding errors
The Proper instances in the context give you roughly those needed on the way
However they don't give the full story. First enable debug Gross gives some tips. https://stackoverflow.com/questions/50989017/proper-instance-for-nested-matching/51015288#51015288
looking for lines with proper_subrelation immediately followed by something about a proper instance tells you when typeclass resolution failed, which typically says something about the instance you need.
unfortunately, typeclass resolution errors show up for a variety of reasons. it's possible such an error message doesn't occur, and the error is actually about something else. the worst part is that you still see the Proper instances in the initial error message (without debugging on), so at first glance it seems like it's a missing instance that's causing the problem.
in particular, you get the same error from insufficiently instantiated arguments. if you try to rewrite with a lemma and don't.
rewrite (@lem _ a) in H.
(* the more common form, where it's unclear *)
rewrite lem in H.
Pass all the arguments to rewrite
as with
What is happening under the hood?
Conclusion
despite its opaqueness and rough edges, the rewriting system is remarkably robust. i was genuinely impressed that it handled my use cases.
this is used in the formalization of staged logic
Thank you to Li Yao on the coq zhlip for patiently answering my questions.