(* --------------------------------------------------------||
||                                                         ||
||            PREDICATES AND RELATIONS IN ROCQ             ||
||                                                         ||
||           Logic and Functional Programming              ||
||                 Florent Schaffhauser                    ||
||                                                         ||
||                 Heidelberg University                   ||
||                   (Summer 2026)                         ||
||                                                         ||
||-------------------------------------------------------- *)

From Coq Require Import Utf8.
From Coq Require Import Lia.

(* In this file, we formalise some of the results on predicates and relations that we have proven in the lectures. *)

(* ----------------------||
||      PREDICATES       ||
||---------------------- *)

(* Recall that a predicate on a set `X` is a function `P : X → Prop`.*)

(* EXAMPLES *)

(* For instance, let us define inductively what it means for a natural number to be even. *)

Inductive IsEven : nat → Prop :=
  dbl (n : nat) : IsEven (n + n).

(* The definition says that `dbl n` is evidence for the fact that `n + n` is even. More formally, this inductive definition formalises the fact that a natural number `m` is even if we can write it as `n + n` for some natural number `n`. For instance, we can prove the following basic results. *)

Example zero_is_even : IsEven 0 := 
  dbl 0.

Example six_is_even : IsEven 6 := 
  dbl 3.

(* Note that `IsEven 3` type-checks. Of course, we want to prove that this proposition does *not* hold, which as usual means that its *negation*, namely the proposition `IsEven 3 → False`, does hold. *)

Check IsEven 3.

(* Proving `IsEven 3 → False` seems not so easy with our definition: one has to show that '3 is divisible by 2' leads to an absurdity. One idea to do that is to argue that if `3 = n + n`, then `3 = 2 * n`, so `3 mod 2 = (2 * n) mod 2`. This should lead to `1 = 0`, which we know is an absurdity. This works if we have an explicitly defined `mod` function (so `3 mod 2` computes to `1`) and a proof (by induction on `n`) that, for all `n : nat`, `(2 * n) mod 2 = 0`. It is possible to follow this approach, but it takes time if we redefine everything.

Alternatively, we can trust Rocq's `Lia` library that we imported at the top of the file. The name `Lia` stands for *linear integer arithmetic* and this library can be used to prove basic inequalities or solve (linear) equations. Note how we use the Unicode character `≤` to denote the inequality `<=` between natural numbers. *)

Example ineq1 (n m : nat) (H : n ≤ m) : 2 * n + 3 ≤ 2 * m + 3.
Proof. 
  lia. 
Qed.

Example solve_eq1 (n : nat) (eq1 : n + 8 = 4 * n + 2) : n = 2.
Proof.
  lia.
Qed.

(* If you want to get a sense of what `lia` does behind the scenes, you can always print the resulting proof term. *)

Print ineq1.
Print solve_eq1.

(* Note that the `lia` tactic is quite powerful: it can for instance use assumptions from the context to close the goal.

Next, to prove that `3` is not even, we use `lia` to assert that `n + n = 3` is impossible. To do so, we will use the `inversion` tactic, which introduces a new equation in our context. Namely, inverting the proof `H` of `IsEven 3` means saying that it suffices to look at the case when this proof is the form `dbl n` for some natural number `n` such that `n + n = 3`. The natural number `n` and the equation `n + n = 3` then get added to the context.

This is similar to what `destruct H eqn: H0` would do, but keeping track of the equation `3 = n + n`. In contrast, `destruct H eqn: H0` keeps track of the equation `H = dbl n`, calling it `H0`, and replaces `IsEven 3` with `IsEven (n + n)` in the context, which is the main purpose of `destruct`. *)

Example three_is_not_even : IsEven 3 → False.
Proof.
  intro H.
  inversion H as [n Hn].
  assert (f : n + n ≠ 3) by lia.
  (* apply f; assumption. *)
  exact (f Hn).
Qed.

Print three_is_not_even.

(* A lot remains of what `lia` does remains hidden, though, even with the following. *)

Set Printing All.
Print three_is_not_even.
Unset Printing All.

(* LOGICALLY EQUIVALENT PREDICATES *)

(* Let `P₁, P₂ : X → Prop` be predicates on `X`. Such predicates are called **equivalent** if, for all `x : X`, the propositions `P₁ x` and `P₂ x` are equivalent, meaning that `P₁ x ↔ P₂ x` holds.

To illustrate this, let us propose an alternative definition of what it means for a natural number to be even. *)

Inductive IsEven' : nat → Prop :=
  | zero_even : IsEven' 0
  | plus_two  : ∀ {n : nat}, IsEven' n → IsEven' (n + 2).

Example zero_is_even' : IsEven' 0 := 
  zero_even.

Example six_is_even' : IsEven' 6 := 
  plus_two (plus_two (plus_two zero_even)).

(* When proving `IsEven' 3 → False`, we note that if we want to give specific names to the data introduced by the `inversion` tactic, we need to consider two cases, because `IsEven'` has two constructors. The data in question should correspond to the *arguments* of the constructors. Since the first constructor, namely `zero_is_even`, has no arguments, the first set of data is empty, hence the line `inversion H as [ | n Hn1 Hn2]` below. This line could also just be `inversion H`, letting Rocq take care of naming the variables.

Note that the `inversion` tactic is also capable of saying that the first case does *not* occur. This is due to the fact that a term of type `IsEven' 3` cannot be introduced by using the constructor `zero_even`. Indeed, `zero_even` only produces terms of type `IsEven' 0` and `0` is different from `3`. Rocq takes care of that for us and does not generate a subgoal corresponding to this case. *)

Example three_is_not_even' : IsEven' 3 → False.
Proof.
  intro H.
  inversion H as [ | n Hn1 Hn2].
  assert (n = 1) as Hn3 by lia.
  rewrite Hn3 in Hn1.
  clear H Hn2 Hn3 n.
    (* Note: The previous three lines can be condensed to two lines using the `replace` tactic. *)
  (* replace n with 1 in Hn1 by lia.
  clear H Hn2. *)
  inversion Hn1.
  assert (n + 1 = 0) by lia.
  clear H H0.
  Search (S _ ≠ 0).
  apply (PeanoNat.Nat.neq_0_succ n).
  Search (_ + 1 = S _).
  rewrite <- PeanoNat.Nat.add_1_r.
  (* symmetry; assumption. *)
  easy.
Qed.

(* We now prove that the propositions `IsEven n` and `IsEven' n` are logically equivalent. *)

Theorem IsEven_iff_IsEven' (n : nat) : IsEven n ↔ IsEven' n.
Proof.
  split.
  - intro H. 
    destruct H.
    induction n as [ | n IHn].
    + simpl. 
      exact zero_even.
    + replace (S n + S n) with ((n + n) + 2) by lia.
      apply plus_two.
      exact IHn.
  - intro H.
    induction H as [ | n Hn].
    + exact (dbl 0).
    + destruct IHHn.
      replace (n + n + 2) with ((n + 1) + (n + 1)) by lia.
      exact (dbl _).
Qed.

(* It is quite instructive to see what happens when we are actually careful in the definition of `IsEven'` and take into account the computational behaviour of the addition of natural numbers (which defines `n + m` by primitive recursion on `n`, not on `m`). Namely, we replace `n + 2` by `2 + n` in the definition. *)

Inductive IsEven'' : nat → Prop :=
  | even_zero : IsEven'' 0
  | two_plus  : ∀ {n : nat}, IsEven'' n → IsEven'' (2 + n).

(* See now how the proof that `3` is not even gets significantly simplified. This makes this third and last definition of being an even natural number the best of the three, in my view. The next topic, on the sum of even numbers, will show similar behaviour. *)

Example three_is_not_even'' : IsEven'' 3 → False.
Proof.
  intro H.
  inversion H.
  inversion H0.
Qed.

(* In contrast, the proof of the equivalence between `IsEven'' n` and `IsEven n` remains essentially unaltered. *)

Theorem IsEven_iff_IsEven'' (n : nat) : IsEven n ↔ IsEven'' n.
Proof.
  split.
  - intro H. 
    destruct H.
    induction n as [ | n Hn].
    + exact even_zero.
    + replace (S n + S n) with (2 + (n + n)) by lia.
      apply two_plus.
      exact Hn.
  - intro H.
    induction H as [ | n Hn].
    + exact (dbl 0).
    + destruct IHHn.
      replace (2 + (n + n)) with ((1 + n) + (1 + n)) by lia.
      exact (dbl _).
Qed.

(* SUM OF EVEN NUMBERS *)

(* Let us compare our three predicates for even numbers on a simple example. Namely, we want to prove that the sum of two even numbers is even. 

With the predicate `IsEven`, Theorem `add_even` does not require an induction on the proof of that `n` is even, but it does require some rewriting (for which we use `lia`). *)

Theorem add_even (n m : nat) : IsEven n → IsEven m → IsEven (n + m).
Proof.
  intros n_even m_even.
  destruct n_even as [p], m_even as [q].
  replace (p + p + (q + q)) with (p + q + (p + q)) by lia.
  (* apply dbl. *)
  constructor.
Qed.

(* With the predicate `IsEven'`, we have to use `induction` and `lia`. *)

Theorem add_even' (n m : nat) : IsEven' n → IsEven' m → IsEven' (n + m).
Proof.
  intros n_even' m_even'.
  induction n_even' as [ | p Hp].
  - assumption.
  - replace (p + 2 + m) with (p + m + 2) by lia.
    constructor. assumption.
Qed.

(* With the predicate `IsEven''`, we only need use `induction`. However, for some reason, the infoview exposes the definition of `Nat.add`, which is not very readable. Fortunately, we can get rid of it using the `fold` tactic, which is optional here. *)

Theorem add_even'' (n m : nat) : IsEven'' n → IsEven'' m → IsEven'' (n + m).
Proof.
  intros n_even'' m_even''.
  induction n_even'' as [ | p Hp].
  + assumption.
  + apply two_plus. 
    (* fold (p + m). *)
    assumption.
Qed.

(* ----------------------||
||      RELATIONS        ||
||---------------------- *)

(* Recall that a relation on a set `X` is  a function `R : X → X → Prop`. For instance, let us define inductively what it means for a natural number `n` to be lower or equal than a natural number `m`. *)

Inductive leq  : nat → nat → Prop :=
 | zero_leq (n : nat) : leq 0 n
 | mono (m n : nat)   : leq m n → leq (S m) (S n).
 
(* Adding the following line makes the constructors of `leq` accessible to automation tactics such as `easy` and `auto`. *)
 
Hint Constructors leq.

Infix "≤" := leq (at level 70, no associativity).
 
Example zero_leq_three : 0 ≤ 3 := 
  zero_leq 3.

Example two_leq_five : 2 ≤ 5 :=
  mono 1 4 (mono 0 3 zero_leq_three).

(* Let us now prove that the relation `leq` is **reflexive**. *)

Theorem leq_reflexive : ∀ n : nat, n ≤ n.
Proof.
  intro n.
  induction n.
  + easy.
  + apply mono.
    assumption.
Qed.

(* As a next step, let us prove that the relation `leq` is also transitive. *)

Theorem leq_transitive : ∀ p q r : nat, p ≤ q → q ≤ r → p ≤ r.
Proof.
  intros.
  revert r H0.
  induction H.
  + easy.
  + intros.
    inversion H0.
    apply (IHleq n0) in H2.
    apply mono.
    assumption.
Qed.

(* To prove that the relation `leq` is anti-symmetric, the following lemma will be helpful. *)

Lemma leq_zero_is_zero : ∀ n : nat, n ≤ 0 → 0 = n.
Proof.
  intros n Hn.
  inversion_clear Hn.
  reflexivity.
Qed.

Theorem leq_antisymmetric : ∀ p q : nat, p ≤ q → q ≤ p → p = q.
Proof.
  intros.
  induction H.
  + apply leq_zero_is_zero.
    assumption.
  + apply f_equal.
    apply IHleq.
    inversion_clear H0.
    assumption.
Qed.
  
(* It is little noticeable because we are so used to it, but the antisymmetry property of `leq` is not just a property of `leq`, it is also a property of `=` on `nat`. Indeed, the type `p ≤ q → q ≤ p → p = q` specified above involves *both* relations. For that reason, it is perhaps useful to try and characterise equality of natural numbers in an elementary way. *)

Inductive nat_eq : nat → nat → Prop :=
  | zero_eq_zero       : nat_eq 0 0
  | succ_eq (p q: nat) : nat_eq p q → nat_eq (S p) (S q).

Infix "≡" := nat_eq (at level 70, no associativity).

(* Let us start by proving that the relation `nat_eq` is reflexive. The proof goes by induction on the batural number `q` in the context. *)

Lemma nat_eq_reflexiv (q : nat) : q ≡ q.
Proof.
  induction q.
  + exact zero_eq_zero.
  + apply succ_eq. 
    assumption.
Qed.

(* Then we can show that `nat_eq` is logically equivalent to the equality relation we have been using all along. *)

Theorem nat_eq_charac (p q : nat) : p ≡ q ↔ p = q.
Proof.
  split.
  + intro.
    induction H.
    - reflexivity.
    - apply f_equal; assumption.
  + intro. 
    rewrite H.
    apply nat_eq_reflexiv.
Qed.

(* ----------------------||
||   PREORDERS, ORDERS,  ||
||    AND EQUIVALENCE    ||
||      RELATIONS        ||
||---------------------- *)

(* We have proven several properties about the relations `leq : nat → nat → Prop` and `nat_eq : nat → nat → Prop`. Using another construct of Rocq, we can be slightly more formal about this, and introduce the **type of equivalence relations on a set**, as well as the **type of orderings on a set**, and show that `nat_eq` and `leq` define terms in those types. 

The abstract construct we need is that of a **record type** in Rocq, which gives us a way to formalise tuples of various kinds. From the point of view of programming language theory, a record can be thought of as an object (the latter being defined conceptually as a list of key-value pairs). *)

(* PREORDERS AND ORDERS *)

(* Let us start with the example of pre-orders. A pre-order on a set `X` is a triple consisting of: 

- a relation `R` on `X`.
- a proof that `R` is symmetric.
- a proof that `R` is transitive.

We can then declare the type of preorders on a set `X` as follows. *)

Record PreOrder (X : Set) := PreOrderMk {
  ord_rel   : X → X → Prop;
  ord_refl  : ∀ x : X, ord_rel x x;
  ord_trans : ∀ x y z : X, ord_rel x y → ord_rel y z → ord_rel x z
}.

(* The resulting term `PreOrder` is a function that takes a set `X` and returns a type. One might expect it to return a set, but the fact that `Prop` is involved in the definition and that `Prop` is not a set in Rocq, accounds for terms like `PreOrder nat` being of sort `Type` and not `Set`. This is a minor theoretical point for us right now, that does not affect how we reason with the collection of preorders. *)

Check PreOrder.
Check PreOrder nat.

(* The function `PreOrderMk`, whose name was chosen explicitly by us, acts like a constuctor for terms of type `PreOrder X`. It will be convenient to make the parameter `X` implicit. *)

Arguments PreOrderMk {X}.
Check @PreOrderMk nat.

(* With all the work we have done, we can construct an explicit order relation on `nat`. *)

Definition pre_order_on_nat : PreOrder nat :=
  PreOrderMk leq leq_reflexive leq_transitive.

(* When a record type is declared, a few functions are created automatically, that correspond to the *fields* (keys) in the record. Here our `PreOrder X` type has three fields, namely `ord_rel`, `ord_refl` and `ord_trans` (these names were chosen by us), so we get three so-called projection functions. 

It will be convenient to make the parameter `X` implicit. Indeed, the first argument of the field projections can usually be inferred in pratice, which is why it is a good idea to make it implicit. *)

Arguments ord_rel {X}.
Arguments ord_refl {X}.
Arguments ord_trans {X}.

Check @ord_rel.
Check @ord_refl.
Check @ord_trans.

(* With these functions, we can access the values corresponding to each field in our record `pre_order_on_nat`. With a bit more work, we could set up a dot notation for it, and write expressions such as `pre_order_on_nat.ord_rel` to access our relation `leq : nat → nat → Prop`. *)

Check ord_rel pre_order_on_nat.

Goal (ord_rel pre_order_on_nat = leq).
Proof.
  reflexivity.
Qed.

Check ord_refl pre_order_on_nat.
Check ord_trans pre_order_on_nat.

Definition pre_order_on_nat' : PreOrder nat.
Proof.
  apply (PreOrderMk leq).
  - exact leq_reflexive.
  - exact leq_transitive.
Qed.

(* Order relations should *extend* preorders, by adding the property that the relation is anti-symmetric. In Rocq, this can be achieved using the following syntax, in which we make `PreOrder` the type of a field of the `Order` structure. *)

Record Order (X : Set) := OrderMk {
  toPreOrder   :> PreOrder X;
  ord_antisymm :  ∀ x y : X, ord_rel toPreOrder x y → ord_rel toPreOrder y x → x = y
}.

Arguments OrderMk {X}.

Definition order_on_nat : Order nat :=
  OrderMk pre_order_on_nat leq_antisymmetric.

(* For every `X : Set`, we get a projection `@toPreOrder X : Order X → Preorder X`. *)

Check toPreOrder.
Arguments toPreOrder {X}.

(* The symbol `:>` in the definition of `Order` registers the projection `toPreorder` as a *coercion* from `Order X` to `PreOrder X`, so Rocq will automatically insert it when a term of type `PreOrder X` is expected but one of type `Order X` is given. 

This makes expression like `ord_rel nat order_on_nat` type-check, even though the function `ord_rel` expects a preorder relation on `nat` and `order_on_nat` is in fact an order relation. *)

Check ord_refl order_on_nat.
Check ord_refl (toPreOrder order_on_nat).

(* We can also remove the symbol `:>` from the declaration `Order X` and implement a coercion to `PreOrder X` by hand, as seen [in a previous lecture](https://matematiflo.github.io/LogicFP2026/Lecture7.html#interpretations). Note that doing this does not affect the next lines of the declaration: `ord_rel X toPreOrder` still type-checks. *)

(* EQUIVALENCE RELATIONS *)

(* Similarly to what we did for preorders, we define equivalence relations on a set `X` as quadruples consisting of: 
  
- a relation `R` on `X`.
- a proof that `R` is reflexive.
- a proof that `R` is symmetric.
- a proof that `R` is transitive.

Note that Rocq lets us introduce the notation `~` for a relation such as `equiv_rel` whilst still in the middle of the declaration of the type `EquivRel`. This is convenient because it makes the next lines much more readable (but you need to 'book' the notation in advance). 

We can also make all the parameters whose types can be inferred from later arguments implicit by default. This will affect the constructor `EquivRelMk` as well as the field projections. Finally, note that, in the declaration `EquivRel`, we specify the type of `EquivRel X` explicitly. *)

Set Implicit Arguments.

Reserved Notation "x ~ y" (at level 70, no associativity).

Record EquivRel (X : Set) : Type := EquivRelMk {
  equiv_rel   : X → X → Prop
    where "x ~ y" := (equiv_rel x y);
  equiv_refl  : ∀ x : X, x ~ x;
  equiv_symm  : ∀ x y : X, x ~ y → y ~ x;
  equiv_trans : ∀ x y z : X, x ~ y → y ~ z → x ~ z
}.

About EquivRelMk.

Fail Check equiv_rel nat.
About equiv_rel.

(* As an exercise, you can show that `nat_eq` is an equivalence relation on `nat`, hence defines a term of type `EquivRel nat`. *)

(* ----------------------||
|| RECORDS AND INDUCTIVE ||
||     DECLARATIONS      ||
||---------------------- *)

(* There is a way to pass from record declarations to inductive declarations with just one constructor and vice-versa. We will not get into details here and just illustrate this with two examples. *)

(* PRODUCT TYPES *)

(* As we know, product types can be declared inductively. *)

Inductive MyProd (X Y : Set) :=
  pair : X → Y → MyProd X Y.

(* They can also be declared as a record. *)

Record MyProd' (X Y : Set) := MyProd'Mk { 
  fst : X;
  snd : Y
}.

(* We see on this example that the arguments of the constructor `pair` for the inductive type `MyProd X Y` are in one-to-one correspondence with the fields of the record `MyProd' X Y`. One advantage of the record approach is that the projections to the fields are generated automatically. *)

(* THE UNIT TYPE *)

(* Another example is the `Unit` type, whose only constructor takes no arguments (atomic constructor). As a consequence, the corresponding record has no fields! *)

Inductive MyUnit :=
  pt : MyUnit.

Record MyUnit' := MyUnit'Mk {}.

(* The set `MyUnit'` has at least one element. *)

Check MyUnit'Mk.

(* In fact, it also has at most one element, in the following sense. *)

Example at_most_one_element : ∀ s t : MyUnit', s = t.
Proof.
  intros.
  destruct s, t.
  reflexivity.
Qed.
