MPRI PRFA — Proof Assistants
🏠 Go back to the course homepage.
Modelling and Automation — Part 1
Records
From Coq Require Import Lia List ZArith. Import ListNotations.
In Coq you can write structures using what we call "records". They consists of various fields that each contain data of a given type.
For instance, a (bad) type of rationals:
Under the hood, Coq will define an inductive type with one constructor called Build_bad_rational1
We can produce values as follows:
The names of the fields also correspond to what we call projections. They let us access the various values of a record.
We need to give different names because numerator is a global definition.
NOTE: To write integers in Z, we can use numeral notations as long as we add %Z at the end to indicate that we don't mean a nat.
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.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.4 <> 0
Actually products that are defined using an inductive type, can also be defined as records. For this we use parame ters 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.
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}.
You can also use do pattern matching using this constructor.
Since there is only one constructor, we can also use let
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.
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}.
We can use records to represent more complex mathematical structures. See for instance the following record representing monoids.
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.x, y, z: natx + y + z = x + y + z
We need Defined to access the fields!
Notation for m_neu 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.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).x, y: natx + 0 + y = x + y
Products of monoids also yields 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.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 class
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 Coq 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.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 Coq. We move the carrier, neutral and operation as parameters because Coq will use them to guess which monoid structure we mean.
Instance is a a special command for definitions that register them as things Coq 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.x, y, z: natx + y + z = x + (y + z)
Some lemmas we can prove using only the monoid structure on (ℕ,0,+). See how Coq 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.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.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
Coq can use type class search to find instances on its own when we use _
exact _.
EXERCISE
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))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 Coq can infer complex monoid structures on its own!
Here is another example of class: decidable equality.
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, Coq finds it on its own.
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.forall x y : nat, (x =? y) = true <-> x = y
We still need to use Defined though.
Automation
Classes are one form of automation. There are others in Coq. 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 auto tactic 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.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.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 Coq automatically uses core, but this behaviour is deprecated and will be removed. Instead, one should do so explicitly:
#[export] Hint Resolve In_hd : core.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 disjointness and difference of constructors.
tauto
For propositional logic. Solves (intuituinistic) propositional tautologies
However, it's easy to use it for classical propositional logic as well:
forall p : Prop, p \/ ~ pforall p : Prop, p \/ ~ pp: Propp \/ ~ pp: Propp \/ ~ ptauto.p: Prop~ ~ (p \/ ~ p)
Or without manual work
Require Import Classical.forall p : Prop, p \/ ~ pforall p : Prop, p \/ ~ ptauto.p: Propp \/ ~ p
intuition / firstorder
Uses the tauto algorithm and calls a tactic on the leafs. tauto is intuition fail, i.e. no leafs allowed. intuition is intuition auto with core
Existential variables, again
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 auto picked for a value
auto also has an eauto variant. You can use it to deal with existential quantifiers. 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.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!
forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4eauto.forall (P : nat -> nat -> Prop) (Q : nat -> Prop), P 4 2 -> (forall x y : nat, P x y -> Q x) -> Q 4