Modelling and automation
- Files:
From Stdlib Require Import Lia List ZArith. Import ListNotations.
Records
In Rocq you can write structures using what we call "records". They consist of various fields that each contain data of a given type.
For instance, a (bad) type of rationals:
Record bad_rational1 := {
numerator : nat ;
denominator : nat
}.Under the hood, Rocq will define an inductive type with one constructor
called Build_bad_rational1.
We can produce values as follows:
Definition half := {|
numerator := 1 ;
denominator := 2
|}.The names of the fields also correspond to what we call projections. They let us access the various values of a record.
Record bad_rational2 := { numerator' : Z ; denominator' : nat }.
We need to give different names because numerator is a global definition.
We can also produce values interactively.
bad_rational2bad_rational2
constructor will ask us to provide the values for all the fields
Znatexact 1%Z.Zexact 4. Defined. Record rational := { numerator'' : Z ; denominator'' : nat ; den_not_zero : denominator'' <> 0 }.nat
We can also produce values interactively.
rationalrationalZ?Goal0 <> 0
refine will turn arguments that can be derived from other arguments into
so-called existential variables (evars)
Abort.rational
the tactic constructor fails because it cannot introduce evars
rational
econstructor can
Abort.Z?denominator'' <> 0rational
same for apply:
rational
eapply works
Z?denominator'' <> 0exact 1%Z.Z?denominator'' <> 0exact H. Abort.H: 4 <> 0?denominator'' <> 0rational?denominator'' <> 0lia. Abort.4 <> 0rationallia. Defined.4 <> 0
Actually, products, which we defined using an inductive type, can also be defined as records. For this we use parameters to records. They work like inductive parameters.
In fact, records are syntactic sugar over inductive types with one constructor that takes all the fields as arguments, and we can even give a name to that constructor.
Record prod' (A B : Type) := mkpair { fst' : A ; snd' : B }. Definition some_pair := mkpair _ _ 12 bool.
Actually it's annoying to have the underscores in mkpair _ _ 12 bool;
we're going to change that with the Arguments command.
Arguments mkpair {A B}. Definition some_other_pair := mkpair false true.
You can also do pattern matching using this constructor.
Definition to_prod {A B} (p : prod' A B) : A * B :=
match p with
| mkpair a b => (a, b)
end.Since there is only one constructor, we can also use let.
Definition to_prod_alt {A B} (p : prod' A B) : A * B :=
let 'mkpair a b := p in (a, b).We can also use a more generic notation that doesn't require remembering
the name of mkpair. It's maybe not as explicit so it's up to you to decide
which version you prefer.
Definition to_prod_alt2 {A B} (p : prod' A B) : A * B :=
let (a, b) := p in (a, b).Generally, one uses the projections directly. They take the parameters as arguments so for convenience we declare them as implicit.
Arguments fst' {A B}. Arguments snd' {A B}. Definition to_prod_alt3 {A B} (p : prod' A B) : A * B := (fst' p, snd' p).
Exercise: Projections
Show that to_prod and to_prod_alt3 produce the same result.
forall (A B : Type) (p : prod' A B), to_prod p = to_prod_alt3 pAdmitted.forall (A B : Type) (p : prod' A B), to_prod p = to_prod_alt3 p
We can use records to represent more complex mathematical structures. See for instance the following record representing monoids.
Record monoid := {
m_ty : Type ;
m_ne : m_ty ;
m_op : m_ty -> m_ty -> m_ty ;
m_l_ne : forall x, m_op m_ne x = x ;
m_r_ne : forall x, m_op x m_ne = x ;
m_assoc : forall x y z, m_op (m_op x y) z = m_op x (m_op y z)
}.Again we can build these structures interactively using refine.
monoidmonoidforall x : nat, 0 + x = xforall x : nat, x + 0 = xforall x y z : nat, x + y + z = x + (y + z)forall x : nat, 0 + x = xreflexivity.x: nat0 + x = xforall x : nat, x + 0 = xx: natx + 0 = xreflexivity.x: natx = xforall x y z : nat, x + y + z = x + (y + z)x, y, z: natx + y + z = x + (y + z)reflexivity. Defined.x, y, z: natx + y + z = x + y + z
We need Defined to access the fields!
Notation for m_ne nat_monoid
We can prove things generically about monoids.
Notation "a ** b" := (m_op _ a b) (at level 11, left associativity).forall (M : monoid) (x y : m_ty M), x ** m_ne M ** y = x ** yforall (M : monoid) (x y : m_ty M), x ** m_ne M ** y = x ** yM: monoid
x, y: m_ty Mx ** m_ne M ** y = x ** y
More complicated than necessary of course
M: monoid
x, y: m_ty Mx ** (m_ne M ** y) = x ** yreflexivity. Qed.M: monoid
x, y: m_ty Mx ** y = x ** yforall x y : nat, x + 0 + y = x + yforall x y : nat, x + 0 + y = x + yapply monoid_fact with (M := nat_monoid). Qed.x, y: natx + 0 + y = x + y
Exercise: Groups as records
Define a record for groups, building on the record for monoids.
Then show that monoid_fact also holds in groups.
Exercise: Lists form a monoid
Show that lists form a monoid using ++ as operation.
How does monoid_fact translate to lists?
Note that, unlike ** or +, ++ associates to the right,
meaning x ++ y ++ z stands for x ++ (y ++ z).
A: TypemonoidAdmitted.A: Typemonoidforall (A : Type) (x y : list A), (x ++ []) ++ y = x ++ yAdmitted.forall (A : Type) (x y : list A), (x ++ []) ++ y = x ++ y
Products of monoids also yield a monoid.
M1, M2: monoidmonoidM1, M2: monoidmonoidM1, M2: monoidforall x : m_ty M1 * m_ty M2, (let '(u, v) := x in (m_ne M1 ** u, m_ne M2 ** v)) = xM1, M2: monoidforall x : m_ty M1 * m_ty M2, (let '(x0, y) := x in fun '(u, v) => (x0 ** u, y ** v)) (m_ne M1, m_ne M2) = xM1, M2: monoidforall x y z : m_ty M1 * m_ty M2, (let '(x0, y0) := (let '(x0, y0) := x in fun '(u, v) => (x0 ** u, y0 ** v)) y in fun '(u, v) => (x0 ** u, y0 ** v)) z = (let '(x0, y0) := x in fun '(u, v) => (x0 ** u, y0 ** v)) ((let '(x0, y0) := y in fun '(u, v) => (x0 ** u, y0 ** v)) z)M1, M2: monoidforall x : m_ty M1 * m_ty M2, (let '(u, v) := x in (m_ne M1 ** u, m_ne M2 ** v)) = xM1, M2: monoid
x: m_ty M1
y: m_ty M2(m_ne M1 ** x, m_ne M2 ** y) = (x, y)reflexivity.M1, M2: monoid
x: m_ty M1
y: m_ty M2(x, y) = (x, y)M1, M2: monoidforall x : m_ty M1 * m_ty M2, (let '(x0, y) := x in fun '(u, v) => (x0 ** u, y ** v)) (m_ne M1, m_ne M2) = xM1, M2: monoid
x: m_ty M1
y: m_ty M2(x ** m_ne M1, y ** m_ne M2) = (x, y)reflexivity.M1, M2: monoid
x: m_ty M1
y: m_ty M2(x, y) = (x, y)M1, M2: monoidforall x y z : m_ty M1 * m_ty M2, (let '(x0, y0) := (let '(x0, y0) := x in fun '(u, v) => (x0 ** u, y0 ** v)) y in fun '(u, v) => (x0 ** u, y0 ** v)) z = (let '(x0, y0) := x in fun '(u, v) => (x0 ** u, y0 ** v)) ((let '(x0, y0) := y in fun '(u, v) => (x0 ** u, y0 ** v)) z)M1, M2: monoid
x: m_ty M1
y: m_ty M2
u: m_ty M1
v: m_ty M2
a: m_ty M1
b: m_ty M2(x ** u ** a, y ** v ** b) = (x ** (u ** a), y ** (v ** b))reflexivity. Defined.M1, M2: monoid
x: m_ty M1
y: m_ty M2
u: m_ty M1
v: m_ty M2
a: m_ty M1
b: m_ty M2(x ** (u ** a), y ** (v ** b)) = (x ** (u ** a), y ** (v ** b))
Type classes
Say we want to use our monoid_fact on pairs of natural numbers.
We can write the following:
But this doesn't tell us much. If we want to write it by hand, it actually doesn't really work because Rocq can't find the monoid structure on its own.
forall a b c d : nat, (a, b) ** (0, 0) ** (c, d) = (a, b) ** (c, d)forall a b c d : nat, (a, b) ** (0, 0) ** (c, d) = (a, b) ** (c, d)
It prints in a readable way, but we had to suffer to write our lemma!
apply monoid_fact. Qed.a, b, c, d: nat(a, b) ** (0, 0) ** (c, d) = (a, b) ** (c, d)
The solution to this problem is called classes. It adapts the notion of type classes one finds in functional programming languages such as Haskell.
Syntax is the same as for records but it is registered as a special structure for Rocq. We move the carrier, neutral and operation as parameters because Rocq will use them to guess which monoid structure we mean.
Class Monoid (A : Type) (neu : A) (op : A -> A -> A) := {
mon_left_neutral : forall x, op neu x = x ;
mon_right_neutral : forall x, op x neu = x ;
mon_assoc : forall x y z, op (op x y) z = op x (op y z)
}.Instance is a special command for definitions that register them as
things Rocq will use automatically if it has to find a value for
Monoid nat 0 plus.
Don't worry too much about the export thing. This is called an attribute
and it just means that if someone imports the file, then the instance will
still be available. We could instead use #[local] to restrict the instance
to the module. It does not matter for the course.
Monoid nat 0 Init.Nat.addMonoid nat 0 Init.Nat.addforall x : nat, 0 + x = xforall x : nat, x + 0 = xforall x y z : nat, x + y + z = x + (y + z)forall x : nat, 0 + x = xreflexivity.x: nat0 + x = xforall x : nat, x + 0 = xlia.x: natx + 0 = xforall x y z : nat, x + y + z = x + (y + z)lia. Qed.x, y, z: natx + y + z = x + (y + z)
A lemma we can prove using only the monoid structure on (ℕ,0,+). See how Rocq infers which proofs we are talking about.
forall k n m : nat, k + n + m + 0 = k + (n + m)forall k n m : nat, k + n + m + 0 = k + (n + m)k, n, m: natk + n + m + 0 = k + (n + m)k, n, m: natk + n + m = k + (n + m)reflexivity. Qed.k, n, m: natk + (n + m) = k + (n + m)
We can prove it in general on monoids with the same proof.
forall (M : Type) (neu : M) (op : M -> M -> M), Monoid M neu op -> forall k n m : M, op (op (op k n) m) neu = op k (op n m)forall (M : Type) (neu : M) (op : M -> M -> M), Monoid M neu op -> forall k n m : M, op (op (op k n) m) neu = op k (op n m)M: Type
neu: M
op: M -> M -> M
H: Monoid M neu op
k, n, m: Mop (op (op k n) m) neu = op k (op n m)M: Type
neu: M
op: M -> M -> M
H: Monoid M neu op
k, n, m: Mop (op k n) m = op k (op n m)reflexivity. Qed.M: Type
neu: M
op: M -> M -> M
H: Monoid M neu op
k, n, m: Mop k (op n m) = op k (op n m)forall k n m : nat, k + n + m + 0 = k + (n + m)forall k n m : nat, k + n + m + 0 = k + (n + m)k, n, m: natk + n + m + 0 = k + (n + m)k, n, m: natMonoid nat 0 Init.Nat.add
Rocq can use type class search to find instances on its own when we use _
exact _. Qed.
Exercise: Product instance
Complete the product instance.
forall (A B : Type) (na : A) (nb : B) (opa : A -> A -> A) (opb : B -> B -> B), Monoid A na opa -> Monoid B nb opb -> Monoid (A * B) (na, nb) (fun '(xa, xb) '(ya, yb) => (opa xa ya, opb xb yb))Admitted.forall (A B : Type) (na : A) (nb : B) (opa : A -> A -> A) (opb : B -> B -> B), Monoid A na opa -> Monoid B nb opb -> Monoid (A * B) (na, nb) (fun '(xa, xb) '(ya, yb) => (opa xa ya, opb xb yb))
Now you can see that Rocq can infer complex monoid structures on its own!
Definition foo : Monoid (nat * nat * nat) _ _ := _.
Exercise: Monoid instance for lists
Define an instance for lists and apply test_monoid to it.
Then check that Rocq can now find a monoid structure on
list bool * nat * list nat.
Hint: If asked for an instance of Monoid, you can always write exact _.
It will trigger class resolution and find it automatically (if you have a
corresponding instance).
forall A : Type, Monoid (list A) [] (app (A:=A))Admitted.forall A : Type, Monoid (list A) [] (app (A:=A))forall (A : Type) (k n m : list A), ((k ++ n) ++ m) ++ [] = k ++ n ++ mAdmitted.forall (A : Type) (k n m : list A), ((k ++ n) ++ m) ++ [] = k ++ n ++ m
Here is another example of a class: decidable equality.
Class Eq A := {
eqb : A -> A -> bool ;
eqb_iff : forall x y, eqb x y = true <-> x = y
}.Note: `` {Eq A} ` is short for {_ : Eq A} -- i.e. we don't have to give it a
name. Indeed we don't care about the name of the instance because we don't
need to refer to it explicitly, Rocq finds it on its own.
Fixpoint memb {A} `{Eq A} (x : A) (l : list A) : bool := match l with | [] => false | y :: l => eqb x y || memb x l end.
Another useful tool is the refine attribute that also lets us give only
partial instances. Note that it only works for Instance, not Definition.
forall x y : nat, (x =? y) = true <-> x = yforall x y : nat, (x =? y) = true <-> x = yapply Nat.eqb_eq. Defined.forall x y : nat, (x =? y) = true <-> x = y
We still need to use Defined though.
Exercise: Specification of memb
Give memb a specification using the In predicate and show it correct with
respect to it.
Exercise: Groups as a class
Define a class of groups and show uniqueness of neutral and inverse for all groups.
Apply this result to a group of your choosing (you will have to provide an
instance). To apply a lemma, sometimes apply is not enough.
In this case you can use eapply which is more permissive. It creates
existential variables (starting with a question mark) corresponding to values
it couldn't guess. They are typically resolved as you prove the various
goals.
Can you show that every group forms a monoid (again, provide a generic instance)?
Automation
Classes are one form of automation. There are others in Rocq, the main one
being tactics. Indeed they can be used to derive properties automatically.
We will start by looking again at the In predicate.
We'll start by proving some easy lemmas.
forall (A : Type) (x : A) (l : list A), In x (x :: l)forall (A : Type) (x : A) (l : list A), In x (x :: l)A: Type
x: A
l: list AIn x (x :: l)A: Type
x: A
l: list Ax = x \/ In x lreflexivity.A: Type
x: A
l: list Ax = x
- Ok, this we already know, but we can use the
autotactic to deal with some of this.
Abort.
forall (A : Type) (x : A) (l : list A), In x (x :: l)forall (A : Type) (x : A) (l : list A), In x (x :: l)A: Type
x: A
l: list AIn x (x :: l)auto. Qed.A: Type
x: A
l: list Ax = x \/ In x lforall (A : Type) (x y : A) (l : list A), In x l -> In x (y :: l)forall (A : Type) (x y : A) (l : list A), In x l -> In x (y :: l)A: Type
x, y: A
l: list A
h: In x lIn x (y :: l)auto. Qed.A: Type
x, y: A
l: list A
h: In x ly = x \/ In x l
Now we would like auto to deal with In without the need to use simpl.
It should also be able to deal with more complex examples like the
following.
forall (A : Type) (x a b c d : A) (l : list A), In x l -> In x (a :: b :: c :: d :: l)forall (A : Type) (x a b c d : A) (l : list A), In x l -> In x (a :: b :: c :: d :: l)A: Type
x, a, b, c, d: A
l: list A
h: In x lIn x (a :: b :: c :: d :: l)A: Type
x, a, b, c, d: A
l: list A
h: In x lIn x (a :: b :: c :: d :: l)
You can see it does nothing.
A: Type
x, a, b, c, d: A
l: list A
h: In x lIn x (b :: c :: d :: l)A: Type
x, a, b, c, d: A
l: list A
h: In x lIn x (c :: d :: l)
This is annoying; let's use repeat:
auto. Abort.A: Type
x, a, b, c, d: A
l: list A
h: In x lIn x l
We don't need this lemma, so I abort in order not to save it.
Applying the lemma by hand is not very nice, and rather tedious.
We're going to declare hints so that auto knows how to use them.
In fact, we're first going to declare a new database of hints dedicated
to In. We call it indb, because why not?
Create HintDb indb.We then add hints to the database using the Hint Resolve command.
Like for instances, we need to specify a locality. It doesn't matter so
we're going to go with export as before.
We then give it the name of a definition (or lemma etc.) and after the
colon the databases to which we want to add the hint.
#[export] Hint Resolve In_hd : indb.forall (A : Type) (x : A) (l : list A), In x (x :: l)forall (A : Type) (x : A) (l : list A), In x (x :: l)A: Type
x: A
l: list AIn x (x :: l)A: Type
x: A
l: list AIn x (x :: l)
It still doesn't work, because auto doesn't use indb by default.
auto with indb.
Now it does!
Abort.If we do not use a HintDb Rocq automatically uses core, but this
behaviour is deprecated and will be removed. Instead, one should do so explicitly:
#[export] Hint Resolve In_hd : core.Exercise: Extending the hint database
Expand the database so that auto with indb is enough to solve the following
goals.
Note: Goal is like Lemma but it doesn't require a name. You won't be able to
reuse the proven goal afterwards, but it's useful to test things.
Recall the following lemmas from the lecture.
forall (A : Type) (x a b c d : A) (l : list A), In x l -> In x (a :: b :: c :: d :: l)Admitted.forall (A : Type) (x a b c d : A) (l : list A), In x l -> In x (a :: b :: c :: d :: l)forall (A : Type) (x a b c d : A) (l : list A), In x (a :: b :: x :: c :: d :: l)Admitted.forall (A : Type) (x a b c d : A) (l : list A), In x (a :: b :: x :: c :: d :: l)
Exercise: Membership in concatenations
Show that In x l implies In x (l ++ r) and that In x r implies
In x (l ++ r).
Now add both lemmas to indb. For the first time, we are introducing a choice
for auto: presented with the goal In x (l ++ r) it has to choose whether to
go left or right. It has no way to know in advance which one is better so it
will pick one and backtrack if it doesn't work.
Use it to solve the following two goals.
forall (A : Type) (x : A) (l r : list A), In x l -> In x (l ++ r)Admitted.forall (A : Type) (x : A) (l r : list A), In x l -> In x (l ++ r)forall (A : Type) (x : A) (l r : list A), In x r -> In x (l ++ r)Admitted.forall (A : Type) (x : A) (l r : list A), In x r -> In x (l ++ r)
forall (A : Type) (x a b c d : A) (l l' : list A), In x l -> In x ([a; b; c] ++ l' ++ d :: l)Admitted.forall (A : Type) (x a b c d : A) (l l' : list A), In x l -> In x ([a; b; c] ++ l' ++ d :: l)forall (A : Type) (x a b c d : A) (l l' : list A), In x l -> In x (l ++ [a; b; c] ++ l' ++ [d])Admitted.forall (A : Type) (x a b c d : A) (l l' : list A), In x l -> In x (l ++ [a; b; c] ++ l' ++ [d])
Exercise: Equal elements
What if the element in my list is not syntactically what I'm looking for but only equal to it?
Prove a lemma for this particular case and add a hint for it. It should solve the following goal.
forall (A : Type) (x y a b c d : A) (l l' : list A), x = y -> In x ([a; b; c] ++ l' ++ d :: y :: l)Admitted.forall (A : Type) (x y a b c d : A) (l l' : list A), x = y -> In x ([a; b; c] ++ l' ++ d :: y :: l)
Domain-specific automation tactics
lia
For linear integer arithmetic. Solves goals about nat / Z and addition,
sometimes multiplication.
congruence
For equality. Solves goals about equality, knows about injectivity and disjointness of constructors.
tauto
For propositional logic. Solves (intuitionistic) propositional tautologies.
However, it's easy to use it for classical propositional logic as well:
Axiom DNE : forall P : Prop, ~~ P -> P.forall p : Prop, p \/ ~ pforall p : Prop, p \/ ~ pp: Propp \/ ~ pp: Propp \/ ~ ptauto. Defined.p: Prop~ ~ (p \/ ~ p)
Or without manual work
From Stdlib Require Import Classical.forall p : Prop, p \/ ~ pforall p : Prop, p \/ ~ ptauto. Defined.p: Propp \/ ~ p
intuition/firstorder
Uses the tauto algorithm and calls a tactic on the leaves.
tauto is intuition fail, i.e. no leaves allowed.
intuition is intuition auto with core
Exercise: The arith database
The equality in the exercise "Equal elements" is not always going to be an
assumption in the context. But it could be handled by some more automation.
There is for instance the arith database that can deal with arithmetic.
You can give two (or more) databases to auto. Check that it indeed succeeds
in the following goal with arith but not without.
forall (a b c d : nat) (l l' : list nat), In (a + b) ([b; c] ++ l' ++ d :: b + a :: l)Admitted.forall (a b c d : nat) (l l' : list nat), In (a + b) ([b; c] ++ l' ++ d :: b + a :: l)
Exercise: Limits of arith
Sadly, it has its limits: check that it isn't sufficient for the following goal even though you should be able to prove it (do it).
Hint: lia is stronger than auto with arith on that one.
forall (a b c d : nat) (l l' : list nat), In (a + b + c) ([a; b; c] ++ l' ++ d :: c + 0 + a + b :: l)Admitted.forall (a b c d : nat) (l l' : list nat), In (a + b + c) ([a; b; c] ++ l' ++ d :: c + 0 + a + b :: l)
Exercise: Using lia in auto (advanced)
There is a way to use lia with auto actually. For this we have to provide
a different kind of hint.
Using the command Hint Extern we can tell Rocq what tactic to try when
encountering a goal of the form n = m where n, m : nat.
Below what we do is say that when the goal is of the shape @eq nat _ _
then we apply lia. We add this to indb.
The 3 is something called cost. auto is actually cost-based and each action
it takes has a cost attached to it. auto will start by picking the actions
with the lowest cost when it has a choice. When it runs out of money, it
gives up.
Check that you can solve the previous goal using auto with indb.
Can you also make it so that the other goal works?
#[export] Hint Extern 3 (@eq nat _ _) => lia : indb.forall (a b c d : nat) (l l' : list nat), In (a + b + c) ([a; b; c] ++ l' ++ d :: c + 0 + a + b :: l)Admitted.forall (a b c d : nat) (l l' : list nat), In (a + b + c) ([a; b; c] ++ l' ++ d :: c + 0 + a + b :: l)
forall (a b c d : Z) (l l' : list Z), In (a + b + c)%Z ([a; b; c] ++ l' ++ d :: (c + d + 0 + a - d + b)%Z :: l)Admitted.forall (a b c d : Z) (l l' : list Z), In (a + b + c)%Z ([a; b; c] ++ l' ++ d :: (c + d + 0 + a - d + b)%Z :: l)
Existential variables
We have seen eapply already. But many tactics come with an e version.
One such example is the eexists tactic that is very convenient. See below.
forall n : nat, n = 0 \/ (exists m : nat, n = S m)forall n : nat, n = 0 \/ (exists m : nat, n = S m)n: natn = 0 \/ (exists m : nat, n = S m)0 = 0 \/ (exists m : nat, 0 = S m)n: natS n = 0 \/ (exists m : nat, S n = S m)auto.0 = 0 \/ (exists m : nat, 0 = S m)n: natS n = 0 \/ (exists m : nat, S n = S m)n: natexists m : nat, S n = S mreflexivity.n: natS n = S ?m
- We're going to show the proof to see what value was picked for the
existential variable.
Qed.
Exercise: Existential variables
Solve the following goal without using any lemma or variable in the context explicitly.
forall (A : Type) (x : A) (l l' : list A), exists y : A, In y (l ++ x :: l')Admitted.forall (A : Type) (x : A) (l l' : list A), exists y : A, In y (l ++ x :: l')
auto also has an eauto variant. You can use it when applying a
hypothesis requires guessing a variable that doesn't appear in the goal (like
y below).
It is generally slower than auto so you can use it when auto fails.
forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4P: nat -> nat -> Prop
Q: nat -> Prop
hP: P 4 2
h: forall x y : nat, P x y -> Q xQ 4assumption. Qed.P: nat -> nat -> Prop
Q: nat -> Prop
hP: P 4 2
h: forall x y : nat, P x y -> Q xP 4 2forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4P: nat -> nat -> Prop
Q: nat -> Prop
hP: P 4 2
h: forall x y : nat, P x y -> Q xQ 4eassumption.P: nat -> nat -> Prop
Q: nat -> Prop
hP: P 4 2
h: forall x y : nat, P x y -> Q xP 4 ?y
Notice how we need the e here too!
Qed.forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4eauto. Qed.forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4
Exercise: eauto
Solve the following goal.
forall (A : Type) (P : A -> A -> Prop) (Q : A -> Prop), (forall x : A, exists y : A, P x y) -> (forall x y : A, P x y -> Q x) -> forall x : A, Q xAdmitted.forall (A : Type) (P : A -> A -> Prop) (Q : A -> Prop), (forall x : A, exists y : A, P x y) -> (forall x y : A, P x y -> Q x) -> forall x : A, Q x
Exercise: Logical equivalences (advanced)
Another interesting feature of Rocq is the ability to rewrite on other things rather than just equality.
For instance, we can use rewrite for logical equivalences. Let us go through it step by step.
To begin, prove the following logical equivalences.
forall A : Prop, A /\ A <-> AAdmitted.forall A : Prop, A /\ A <-> Aforall A : Prop, A \/ A <-> AAdmitted.forall A : Prop, A \/ A <-> Aforall A B : Prop, A /\ B <-> B /\ AAdmitted.forall A B : Prop, A /\ B <-> B /\ Aforall A B : Prop, A \/ B <-> B \/ AAdmitted.forall A B : Prop, A \/ B <-> B \/ Aforall A B C : Prop, (A /\ B) /\ C <-> A /\ B /\ CAdmitted.forall A B C : Prop, (A /\ B) /\ C <-> A /\ B /\ C
Exercise: Rewriting with equivalences (advanced)
Now you can solve the following goals just by using rewrite on equivalences. No more need for split!
Note that you can also use reflexivity, symmetry and transitivity on those.
Under the hood, this is using class resolution to find the proper instances.
For iff (logical equivalence) they are already registered so we can freely
use them.
forall A B C D : Prop, A /\ (B /\ B) /\ (C -> D) /\ (C -> D) <-> A /\ B /\ (C -> D)Admitted.forall A B C D : Prop, A /\ (B /\ B) /\ (C -> D) /\ (C -> D) <-> A /\ B /\ (C -> D)forall A B : Prop, B /\ A /\ B \/ A /\ B <-> B /\ AAdmitted.forall A B : Prop, B /\ A /\ B \/ A /\ B <-> B /\ A
Exercise: Set equality on lists (advanced)
Ok, this is getting old. We're going to switch to another relation which isn't already registered in the standard library. First we're going to introduce a relation on lists that relates two lists if they have the same elements (regardless of how many times they appear), i.e., if they represent the same set.
Complete the following definition.
Definition list_set_eq {A} (u v : list A) : Prop := REPLACE_ME. Notation "u ~ v" := (list_set_eq u v) (at level 70).
We first need to show that the relation is reflexive, symmetric and
transitive. We do so by instantiating the right classes so that we can use
the corresponding tactics (as for iff).
Check it works with the provided goal.
From Stdlib Require Import Setoid.forall A : Type, Reflexive list_set_eqAdmitted.forall A : Type, Reflexive list_set_eqforall A : Type, Symmetric list_set_eqAdmitted.forall A : Type, Symmetric list_set_eqforall A : Type, Transitive list_set_eqAdmitted.forall A : Type, Transitive list_set_eqforall (A : Type) (u v w : list A), v ~ u -> (u ~ u -> v ~ w) -> u ~ wAdmitted.forall (A : Type) (u v w : list A), v ~ u -> (u ~ u -> v ~ w) -> u ~ w
Exercise: Rewriting with set equality (advanced)
We will use the following command to register this relation to use with rewrite. Rocq will find the instances of equivalence relation we provided above. Otherwise we could provide them manually. Look up the documentation if you want to do that: https://rocq-prover.org/doc/master/refman/addendum/generalized-rewriting.html
On the left of the colon we give the list of parameters, here there is only
one which is the type of values. We call it A and we can refer to it after
the colon.
The next argument is the type of elements we compare with the relation:
list A.
Then we give the relation itself: list_set_eq.
Finally we give a name to the setoid relation. This is for Rocq's internal use.
Prove the following goal using rewrite. This is just transitivity again but this is to show you how it works on a simple example.
Add Parametric Relation A : (list A) list_set_eq as list_set_rel.forall (A : Type) (u v w : list A), u ~ v -> v ~ w -> u ~ wAdmitted.forall (A : Type) (u v w : list A), u ~ v -> v ~ w -> u ~ w
Prove that u ++ v ~ v ++ u.
forall (A : Type) (u v : list A), u ++ v ~ v ++ uAdmitted.forall (A : Type) (u v : list A), u ++ v ~ v ++ u
Exercise: Morphisms (advanced)
In order to rewrite this relation inside, e.g., In, we have to first show
that In is invariant under ~. In other words, that u ~ v implies
In x u <-> In x v.
We can use the following command to declare it. This will in turn let us
rewrite inside In.
Prove that we indeed have a morphism. Prove the given goals using rewrite.
A: Type
x: Aforall x0 y : list A, x0 ~ y -> In x x0 <-> In x yAdmitted.A: Type
x: Aforall x0 y : list A, x0 ~ y -> In x x0 <-> In x yforall (A : Type) (x : A) (u v : list A), In x (u ++ v) -> In x (v ++ u)Admitted.forall (A : Type) (x : A) (u v : list A), In x (u ++ v) -> In x (v ++ u)forall (A : Type) (x a b c : A) (u v : list A), In x (u ++ [a; b; c] ++ x :: v)Admitted.forall (A : Type) (x a b c : A) (u v : list A), In x (u ++ [a; b; c] ++ x :: v)
Look up the Forall predicate. Show that it is a morphism for list_set_eq.
Proceed to solve the following goal.
forall u v w : list nat, let P := fun n : nat => exists m : nat, n = 2 * m in Forall P (w ++ u ++ v) -> Forall P (u ++ v ++ w)Admitted.forall u v w : list nat, let P := fun n : nat => exists m : nat, n = 2 * m in Forall P (w ++ u ++ v) -> Forall P (u ++ v ++ w)