PRFA.live_coding4
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.
Compute (numerator half).
Compute (denominator half).
Record bad_rational2 := {
numerator' : Z ;
denominator' : nat
}.
(* 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.
Definition quarter : bad_rational2.
Proof.
(* constructor will ask us to provide the values for all the fields *)
constructor.
- exact 1%Z.
- exact 4.
Defined.
Record rational := {
numerator'' : Z ;
denominator'' : nat ;
den_not_zero : denominator'' ≠ 0
}.
About Build_rational.
Print quarter.
Proof.
(* constructor will ask us to provide the values for all the fields *)
constructor.
- exact 1%Z.
- exact 4.
Defined.
Record rational := {
numerator'' : Z ;
denominator'' : nat ;
den_not_zero : denominator'' ≠ 0
}.
About Build_rational.
Print quarter.
We can also produce values interactively.
Definition quarter'' : rational.
Proof.
refine (Build_rational _ _ _).
(* refine will turn arguments that can be derived from other arguments into
so-called existential variables (evars) *)
Restart.
(* the tactic constructor fails because it cannot introduce evars *)
Fail constructor.
(* econstructor can *)
econstructor.
Restart.
(* same for apply: *)
Fail apply Build_rational.
(* eapply works *)
eapply Build_rational.
- exact 1%Z.
- assert (4 ≠ 0) as H by lia.
exact H.
Restart.
eapply (Build_rational 1).
instantiate (1 := 4). lia.
Restart.
refine {| numerator'' := 1 ; denominator'' := 4 |}.
lia.
Defined.
Proof.
refine (Build_rational _ _ _).
(* refine will turn arguments that can be derived from other arguments into
so-called existential variables (evars) *)
Restart.
(* the tactic constructor fails because it cannot introduce evars *)
Fail constructor.
(* econstructor can *)
econstructor.
Restart.
(* same for apply: *)
Fail apply Build_rational.
(* eapply works *)
eapply Build_rational.
- exact 1%Z.
- assert (4 ≠ 0) as H by lia.
exact H.
Restart.
eapply (Build_rational 1).
instantiate (1 := 4). lia.
Restart.
refine {| numerator'' := 1 ; denominator'' := 4 |}.
lia.
Defined.
Actually products that are 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.
Print some_pair.
Actually it's annoying to have the underscores in mkpair _ _ 12 bool
we're going to change that with the Arguments command.
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}.
Definition to_prod_alt3 {A B} (p : prod' A B) : A × B :=
(fst' p, snd' 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 : ∀ x, m_op m_ne x = x ;
m_r_ne : ∀ x, m_op x m_ne = x ;
m_assoc : ∀ 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.
Definition nat_monoid : monoid.
Proof.
refine {|
m_ty := nat ;
m_ne := 0 ;
m_op n m := n + m
|}.
- intro. reflexivity.
- intro. rewrite <- plus_n_O. reflexivity.
- intros x y z. rewrite Nat.add_assoc. reflexivity.
Defined. (* We need Defined to access the fields! *)
Compute nat_monoid.(m_ne). (* Notation for m_neu nat_monoid *)
Check nat_monoid.(m_op).
We can prove things generically about monoids.
Notation "a ** b" := (m_op _ a b) (at level 11, left associativity).
Check m_assoc.
Lemma monoid_fact :
∀ (M : monoid) (x y : M.(m_ty)),
x ** M.(m_ne) ** y = x ** y.
Proof.
intros M x y.
(* More complicated than necessary of course *)
rewrite m_assoc. rewrite m_l_ne. reflexivity.
Qed.
Lemma nat_fact :
∀ (x y : nat),
x + 0 + y = x + y.
Proof.
intros x y.
apply monoid_fact with (M := nat_monoid).
Qed.
Products of monoids also yields a monoid.
Definition prod_monoid (M1 M2 : monoid) : monoid.
Proof.
refine {|
m_ty := M1.(m_ty) × M2.(m_ty) ;
m_ne := (M1.(m_ne), M2.(m_ne)) ;
m_op '(x,y) '(u,v) := (x ** u, y ** v)
|}.
- intros [x y]. rewrite !m_l_ne. reflexivity.
- intros [x y]. rewrite !m_r_ne. reflexivity.
- intros [x y] [u v] [a b]. rewrite !m_assoc. reflexivity.
Defined.
Type classes
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.
Fail Lemma nat_nat_fact :
∀ (a b c d : nat),
(a, b) ** (0, 0) ** (c, d) = (a, b) ** (c, d).
Lemma nat_nat_fact :
∀ (a b c d : nat),
((prod_monoid nat_monoid nat_monoid).(m_op) (a, b) (0, 0)) ** (c, d) =
(prod_monoid nat_monoid nat_monoid).(m_op) (a, b) (c, d).
Proof.
(* It prints in a readable way, but we had to suffer to write our lemma! *)
intros a b c d.
apply monoid_fact.
Qed.
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.
Class Monoid (A : Type) (neu : A) (op : A → A → A) := {
mon_left_neutral : ∀ x, op neu x = x ;
mon_right_neutral : ∀ x, op x neu = x ;
mon_assoc : ∀ x y z, op (op x y) z = op x (op y z)
}.
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.
#[export] Instance MonoidNat : Monoid nat 0 plus.
Proof.
split.
- intro. reflexivity.
- intro. lia.
- intros. lia.
Qed.
Proof.
split.
- intro. reflexivity.
- intro. lia.
- intros. lia.
Qed.
Some lemmas we can prove using only the monoid structure on (ℕ,0,+).
See how Coq infers which proofs we are talking about.
Lemma test_nat :
∀ k n m, k + n + m + 0 = k + (n + m).
Proof.
intros.
rewrite mon_right_neutral.
rewrite mon_assoc. reflexivity.
Qed.
∀ k n m, k + n + m + 0 = k + (n + m).
Proof.
intros.
rewrite mon_right_neutral.
rewrite mon_assoc. reflexivity.
Qed.
We can prove it in general on monoids with the same proof.
Lemma test_monoid :
∀ M neu op `{Monoid M neu op} k n m,
op (op (op k n) m) neu = op k (op n m).
Proof.
intros.
rewrite mon_right_neutral.
rewrite mon_assoc. reflexivity.
Qed.
Lemma test_nat' :
∀ k n m, k + n + m + 0 = k + (n + m).
Proof.
intros. apply test_monoid.
(* Coq can use type class search to find instances on its own when we use _ *)
exact _.
Qed.
∀ M neu op `{Monoid M neu op} k n m,
op (op (op k n) m) neu = op k (op n m).
Proof.
intros.
rewrite mon_right_neutral.
rewrite mon_assoc. reflexivity.
Qed.
Lemma test_nat' :
∀ k n m, k + n + m + 0 = k + (n + m).
Proof.
intros. apply test_monoid.
(* Coq can use type class search to find instances on its own when we use _ *)
exact _.
Qed.
EXERCISE
Complete the product instance.
#[export] Instance MonoidProd :
∀ A B na nb opa opb,
Monoid A na opa →
Monoid B nb opb →
Monoid (A × B) (na,nb) (fun '(xa,xb) '(ya,yb) ⇒ (opa xa ya, opb xb yb)).
Proof.
Admitted.
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.
Fixpoint memb {A} `{Eq A} (x : A) (l : list A) : bool :=
match l with
| [] ⇒ false
| y :: l ⇒ eqb x y || memb x l
end.
Search (nat → nat → bool).
Another useful tool is the refine attribute that also lets us give only
partial instances. Note that it only works for Instance, not Definition.
#[export, refine] Instance Eq_nat : Eq nat := {|
eqb := Nat.eqb
|}.
Proof.
Search Nat.eqb.
apply Nat.eqb_eq.
Defined. (* We still need to use Defined though. *)
Compute (eqb 0 1).
Compute (eqb 12 12).
Compute (memb 1 [ 3 ; 5 ; 1 ]).
Compute (memb 12 [ 3 ; 5 ; 1 ]).
Automation
Ok, this we already know, but we can use the auto tactic to deal with
some of this.
Restart.
intros A x l.
simpl. auto.
Qed.
Lemma In_tl :
∀ A (x y : A) l,
In x l →
In x (y :: l).
Proof.
intros A x y l h.
simpl. auto.
Qed.
intros A x l.
simpl. auto.
Qed.
Lemma In_tl :
∀ A (x y : A) l,
In x l →
In x (y :: l).
Proof.
intros A x y l h.
simpl. auto.
Qed.
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.
Lemma In_4 :
∀ A (x a b c d : A) l,
In x l →
In x (a :: b :: c :: d :: l).
Proof.
intros A x a b c d l h.
auto. (* You can see it does nothing. *)
apply In_tl. apply In_tl. (* This is annoying let's use repeat: *)
repeat apply In_tl.
auto.
Abort. (* 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.
If we do not use a HintDB Coq automatically uses core
Succeed
#[export] Hint Resolve In_hd.
Succeed
#[export] Hint Resolve In_hd : core.
Lemma In_hd' :
∀ A (x : A) l,
In x (x :: l).
Proof.
intros A x l.
auto. (* It still doesn't work, because auto doesn't use indb by default. *)
auto with indb. (* Now it does! *)
Abort.
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:Axiom DNE : ∀ P : Prop, ~~ P → P.
Goal ∀ p, p ∨ ¬ p.
Proof.
intros. Fail tauto. apply DNE. tauto.
Defined.
Or without manual work
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 coreExistential variables, again
Goal ∀ n, n = 0 ∨ ∃ m, n = S m.
Proof.
intro n.
destruct n.
- auto.
- right. eexists. reflexivity.
(* We're going to show the proof to see what auto picked for a value *)
Show Proof.
Qed.
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.