PRFA.live_coding4

MPRI PRFA (2-7-2) / 2024 / Live coding session 4

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:

Record bad_rational1 := {
  numerator : nat ;
  denominator : nat
}.

Under the hood, Coq will define an inductive type with one constructor called Build_bad_rational1
Print bad_rational1.
About 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.

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.

Definition half' := {|
  numerator' := 1%Z ;
  denominator' := 2
|}.

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.

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.

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.

Arguments mkpair {A B}.

Definition some_other_pair := mkpair false true.

You can also use 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).

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

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.

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.

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.

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.

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!
Definition foo : Monoid (nat × nat × nat) _ _ := _.
Print foo.

Here is another example of class: decidable equality.

Class Eq A := {
  eqb : A A bool ;
  eqb_iff : 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, Coq finds it on its own.

Fixpoint memb {A} `{Eq A} (x : A) (l : list A) : bool :=
  match l with
  | []false
  | y :: leqb 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

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.

Lemma In_hd :
   A (x : A) l,
    In x (x :: l).
Proof.
  intros A x l.
  simpl. left. reflexivity.
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.

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
Require Import Classical.

Goal p, p ¬ p.
Proof.
  intros. tauto.
Defined.

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


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.

Goal (P : nat nat Prop) (Q : nat Prop),
  P 4 2
  ( x y, P x y Q x)
  Q 4.
Proof.
  intros P Q hP h.
  apply h with (y := 2). assumption.
Restart.
  intros P Q hP h.
  eapply h.
  eassumption. (* Notice how we need the e here too! *)
Restart.
  eauto.
Qed.