From mathcomp Require Import all_ssreflect.
Set Implicit Arguments.
Unset Strict Implicit.
Unset Printing Implicit Defensive.
Definition sorry {T} : T. Admitted.
Set Implicit Arguments.
Unset Strict Implicit.
Unset Printing Implicit Defensive.
Definition sorry {T} : T. Admitted.
Brief recap:
- What is a Coq proof, its justification of truth.
- Typing judgments Γ ⊢ p : T
- Basic inductive types
- Library built-ins for logic and data types (nat, bool, list)
- Tension / duality of computation vs propositions
- Context management in interactive proofs, tacticals ⇒, : for stack manipulation
- Tactics have rewrite, move, apply, case, elim, simpl, ∃
- Intro patterns // and /=
Key points to see today:
- Reflection, views, characteristic predicates (30 mins)
- Smarter case analysis (15 mins)
- Structures and class hiearchies: subtypes (tuples), choice, finite types (45 mins)
Reflection and proving with computation
About le.
Print le.
About leq.
Print leq.
Lemma le_1 : 100 ≤ 200.
Proof. Admitted.
Print le_1.
Lemma le_2 : (100 ≤ 200)%coq_nat.
Proof. Admitted.
Print le_2.
Lemma lt_1 n : ~~ (n < n).
Proof. Admitted.
Lemma lt_2 n : ¬ (n < n)%coq_nat.
Proof. Admitted.
Print le.
About leq.
Print leq.
Lemma le_1 : 100 ≤ 200.
Proof. Admitted.
Print le_1.
Lemma le_2 : (100 ≤ 200)%coq_nat.
Proof. Admitted.
Print le_2.
Lemma lt_1 n : ~~ (n < n).
Proof. Admitted.
Lemma lt_2 n : ¬ (n < n)%coq_nat.
Proof. Admitted.
Moreover, the math-comp version, by virtue of being in bool,
inherits some nice properties, like decidability, for free.
But there are often many cases where the propositional version may be
more convenient. The canonical example is case analysis on evidence: a
proof of P ∨ Q can be analyzed directly using pattern matching or
the case tactics, whereas a proof of P || Q = true requires a way
more involved analyse.
Assume a proposition P in Prop, that has a counterpart in bool
such that "P provable if and only if p = true". Thus, ¬ P is
provable iff p = false. In a sense, we want to state P ↔ p, but typing
doesn't quite match.
This equivalence relation is captured via the reflect datatype:
reflect P p contains evidence for the result of the computation
p, together with a proof of the propositional property P or ¬P
depending on the outcome of p.
The math-comp library take advantage of this predicate extensively,
and provides special support to ease its use via views.
Views (https://coq.inria.fr/refman/proof-engine/ssreflect-proof-language.htmlviews-and-reflection) are a lightly programmable mechanism to transform equivalent propositions among
themselves.
Views are understood by intro patterns and [apply], and are usually applied with a [/view]
syntax. Let's see an example:
Section View.
Variables (P Q R S : Prop).
Axiom view1 : P → Q.
Axiom view2 : P ↔ R.
Axiom view3 : Q ↔ S.
Lemma v1 : R → S.
Proof.
move/view2/view1⇒ pQ. apply/view3.
Admitted.
End View.
Variables (P Q R S : Prop).
Axiom view1 : P → Q.
Axiom view2 : P ↔ R.
Axiom view3 : Q ↔ S.
Lemma v1 : R → S.
Proof.
move/view2/view1⇒ pQ. apply/view3.
Admitted.
End View.
Views do understand the reflect predicate, we thus can use them in proofs in
a convenient way:
Note the suffix P which often denotes a reflection of "characteristic lemma"
Using case analysis effectively:
About andP.
Lemma andE (b1 b2 : bool) : (b1 ∧ b2) ↔ (b1 && b2).
Proof. by split ⇒ /andP. Qed.
Lemma test (b1 b2 : bool) : if (b1 && b2) then b1 else ~~(b1||b2).
Proof.
move: (@andP b1 b2).
move: (@andE b1 b2).
Admitted.
Lemma andE (b1 b2 : bool) : (b1 ∧ b2) ↔ (b1 && b2).
Proof. by split ⇒ /andP. Qed.
Lemma test (b1 b2 : bool) : if (b1 && b2) then b1 else ~~(b1||b2).
Proof.
move: (@andP b1 b2).
move: (@andE b1 b2).
Admitted.
The above looked like magic! But indeed, let's review the reflect predicate again:
Indeeed, the relevant parameter appears as an index! math-comp provides special
support for this (try case: andP), and we have other options like ifP, boolP, etc...
This trick can be used with our own custom predicate, for example see the powerful leqP
predicate:
About leqP.
Print leq_xor_gtn.
Print leq_xor_gtn.
Some other intersting views are:
About fingraph.dfsP.
About path.pathP.
About path.splitP.
About path.pathP.
About path.splitP.
A similar technique can be used to implement "small inversion", avoiding many of the
pitfalls that regular inversion has in terms of proof maintenance.
Once we have some basics of writing programs and proofs in Coq sorted out,
the next natural question is how to organize them into libraries.
For example, types may have some common
property such as decidability of equality, countability, or finiteness. It'd be nice if we
could have a single generic theory for all types sharing some common traits that we could share.
A good example is indeed types that have decidable equality.
Let's starts by proving this statement about equality and natural numbers:
Organizing common properties and libraries:
Types with Decidable Equality
This is a lemma that may need to use often, for example to reason about an indexing function.
However, we already have the eqn function that in a sense does exactly the same than the proof, and
moreover, it is faster to run. So we can maybe try to relate them with reflect?
That looks quite good, now we can prove the previous lemma in a bit more convenient way:
But not only nat has decidable equality, but many other types do, and we'd like to reason in our proofs with their richer theory.
So in order to organize this, we will declare an interface
eqType for types with decidable equality:
Print eqType.
Print Equality.type.
Print Equality.mixin_of.
Print Equality.axiom.
Print Equality.type.
Print Equality.mixin_of.
Print Equality.axiom.
We did print the real thing as it is often convenient to see the not so rosy reality
of optimized libraries. The above is the start of the implementation of a "mixin"
object-oriented design pattern, so
other structures can inherit efficiently from eqType, which in fact they do!
Thanks to eqType we can now do some more interesting stuff. Let's do the proof again:
Note that there was a quite magic process in which Coq realized that nat
has an eqType instance. The mechanism implementing this is called "Canonical
Structures" and it is a form of inference that can be triggered by unification.
The implementation is a low level detail, and end-users have better tools this days, for
example "Hierarchy Builder" that will help them manage all these structures.
Other systems like Lean have built-in support (using type classes). Having eqType available allows us to
have some nice functions, and a membership predicate:
About undup.
Lemma size_dedup (T : eqType) (s : seq T) : size (undup s) ≤ size s.
Proof. Admitted.
Search _ list eqType.
Lemma mem_filter (T : eqType) (a : pred T) (x : T) (s : seq T) :
(x \in [seq x <- s | a x]) = a x && (x \in s).
Proof. Admitted.
Search _ list (mem _).
Lemma size_dedup (T : eqType) (s : seq T) : size (undup s) ≤ size s.
Proof. Admitted.
Search _ list eqType.
Lemma mem_filter (T : eqType) (a : pred T) (x : T) (s : seq T) :
(x \in [seq x <- s | a x]) = a x && (x \in s).
Proof. Admitted.
Search _ list (mem _).
That's great, and moreover math-comp provides facilities to build
class instances for isomorphic types. This saves a lot of time and
effort.
math-comp provides a very rich hierarchy or algebraic structures,
which we will explore soon. See
https://math-comp.github.io/htmldoc/libgraph.html
Let's explore that graph and the documentation a bit.
Some relevant classes at this stage are:
"subtypes" are a key building block of many objects in math-comp. The key idea stems
from the fact that boolean predicates on types have a single inhabitant, thus they are
"irrelevant" in terms of equality. We can then just ignore the predicate when comparing two elements of a subType. math-comp will infer a subType structure automatically in many
cases:
- eqType: types with decidable equality, operator ==, main theory
- eqP subType P A: types that meet a decidable (thus irrelevant) predicate; subTypes of A convertibles to A. For example the set of all even natural numbers: { x : nat | even x }.
- choiceType: eqTypes with a choice principle, main theory xchoose, xchooseP
- countType: choiceTypes that are in bijection with the naturals, pickle, unpickle
- finType: countTypes that are finite, large theory of cardinality, decidable quantification, etc...
SubTypes
About val_inj.
Definition enat : @subType nat _ := { x : nat | ~~ odd x }.
About enat.
Lemma even_nat_add (x y : enat) : val x = val y → x = y.
Proof. Admitted.
Search subType.
Definition e5 : option enat := insub_eq enat 5.
Definition e6 : option enat := insub_eq enat 6.
Compute e5.
Compute e6.
Definition enat : @subType nat _ := { x : nat | ~~ odd x }.
About enat.
Lemma even_nat_add (x y : enat) : val x = val y → x = y.
Proof. Admitted.
Search subType.
Definition e5 : option enat := insub_eq enat 5.
Definition e6 : option enat := insub_eq enat 6.
Compute e5.
Compute e6.
About finType.
Print Finite.type.
Print Finite.class_of.
Print Finite.mixin_of.
Print Finite.axiom.
Print Finite.type.
Print Finite.class_of.
Print Finite.mixin_of.
Print Finite.axiom.
The axiom states that every element of the type appears once in the enumeration. Note how this promises
nothing about the order! Thus, proofs relying on the enumeration of the elements for a finite type need to work up to permutation of the list.
(We have not covered this in the class, but math-comp provides very powerful tools to reason up to permutation. Finite types provide some operations related to cardinalatiy, and
the corresponding boolean quantifiers.
About forallP.
About forallb_tnth.
About forallb_tnth.
As it is usual in mathcomp, types built from other finTypes are automatically inferred to be a finType:
Ordinals and tuples
Subtypes have many applications in mathematics and computer science. Two of the core math-comp building types are ordinal and tuple:
Print ordinal.
Print tuple_of.
Print tuple_of.
One core advante of subtypes is that they allow to reuse the existing operators, for example, we can map easily over a tuple:
Lemma tuple_1 T U n (t : n.-tuple T) (f : T → U) : size (map f t) = size t.
Proof. Admitted.
Lemma tuple_2 T n (t : n.-tuple T) : [tuple of map id t] = t.
Proof. Admitted.
Proof. Admitted.
Lemma tuple_2 T n (t : n.-tuple T) : [tuple of map id t] = t.
Proof. Admitted.
A tuple over a finite type is a finite type. Tuples are of fundamental importance as we will use them to build finitely supported functions which are the base for matrices.
As we have seen, it is customary to pack the definition of an
structure and its properties into a record. For example, types with
decidable equality are packed as eqType, which thanks to a coercion
can be used a Type.
However, it is very common that we will have the following situation:
(∀ {T : eqType}, (x y : T), ...) 3 4
That is to say, x y have type ?T.(type) (due to the coercion), as
?T is implicit and just a meta-variable at this point.
Thus, we got a unification problem ?T.(type) = nat, which is hard to
solve as it would mean we need to synthesize an implementation de
eqType for nat on on the fly.
Instead, we can tell Coq to consider a hint when this kind of
unification problem appears. What we can say to Coq is that indeed,
every unification problem that that has the shape of ?T : eqType |-
?T.(type) = nat , should be solved by using the existing nat_eqP
instance.
"Canonical" structures provides us the way to indicate this.
Coercions allow the type inference to relate to different types by
inserting (or synthesising) a type casting from a type to another. A
good example is just the above equation. We have T : eqType and we
need in (x : T) that T is a Type however it is an eqType. But
Coq knows how to repair this, and will insert the .(type) projection
so we now have (x : T.(type), with this correctly typing.
math-comp allows to build instances more easily by showing a bijection
to a type with an existing instance:
Canonical Structures
Coercions
Building instances easily
About PcanEqMixin.
Status in Coq
A small declarative interpreter
Finitely-many literals.
Variable symbol : finType.
Variable domain : finType.
Variable ar : {ffun symbol → nat}.
Inductive lit := Lit : ∀ s : symbol, (ar s).-tuple domain → lit.
Variable domain : finType.
Variable ar : {ffun symbol → nat}.
Inductive lit := Lit : ∀ s : symbol, (ar s).-tuple domain → lit.
Build a finType instance for lit
Section LitFinType.
Notation max_ar := (\max_(s in symbol) ar s).
Lemma max_ar_bound y : ar y < max_ar.+1.
Proof. exact/leq_bigmax_cond. Qed.
Notation plit := ({x : 'I_(max_ar.+1) & (symbol × x.-tuple domain)%type}).
Definition lit_to_plit (l : lit) : plit :=
let: Lit s args := l in existT _ (Ordinal (max_ar_bound s)) (s, args).
Definition plit_to_lit (e : plit): option lit.
case: e ⇒ x [s]; case: (val x == ar s) / eqP ⇒ [-> | _] args;
[exact/Some/(Lit args)|exact/None].
Defined.
Lemma lit_to_plitK : pcancel lit_to_plit plit_to_lit.
Proof. by case⇒ [s args] /=; case: eqP ⇒ // ?; rewrite !eq_axiomK. Qed.
Canonical lit_eqType := Eval hnf in EqType lit (PcanEqMixin lit_to_plitK).
Canonical lit_choiceType := Eval hnf in ChoiceType lit (PcanChoiceMixin lit_to_plitK).
Canonical lit_countType := Eval hnf in CountType lit (PcanCountMixin lit_to_plitK).
Canonical lit_finType := Eval hnf in FinType lit (PcanFinMixin lit_to_plitK).
End LitFinType.
Notation max_ar := (\max_(s in symbol) ar s).
Lemma max_ar_bound y : ar y < max_ar.+1.
Proof. exact/leq_bigmax_cond. Qed.
Notation plit := ({x : 'I_(max_ar.+1) & (symbol × x.-tuple domain)%type}).
Definition lit_to_plit (l : lit) : plit :=
let: Lit s args := l in existT _ (Ordinal (max_ar_bound s)) (s, args).
Definition plit_to_lit (e : plit): option lit.
case: e ⇒ x [s]; case: (val x == ar s) / eqP ⇒ [-> | _] args;
[exact/Some/(Lit args)|exact/None].
Defined.
Lemma lit_to_plitK : pcancel lit_to_plit plit_to_lit.
Proof. by case⇒ [s args] /=; case: eqP ⇒ // ?; rewrite !eq_axiomK. Qed.
Canonical lit_eqType := Eval hnf in EqType lit (PcanEqMixin lit_to_plitK).
Canonical lit_choiceType := Eval hnf in ChoiceType lit (PcanChoiceMixin lit_to_plitK).
Canonical lit_countType := Eval hnf in CountType lit (PcanCountMixin lit_to_plitK).
Canonical lit_finType := Eval hnf in FinType lit (PcanFinMixin lit_to_plitK).
End LitFinType.
Now you can read from here
Interpretations are set of literals
A literal is true in i if it belongs (the negated version would be \isnot) Notation lit_true i l := (l \in i).
We define naïve clauses.
Inductive clause := Clause of lit & seq lit.
Definition head cl := let: Clause h b := cl in h.
Definition body cl := let: Clause h b := cl in b.
Implicit Types (i m : interp) (cl : clause) (l : lit).
Definition head cl := let: Clause h b := cl in h.
Definition body cl := let: Clause h b := cl in b.
Implicit Types (i m : interp) (cl : clause) (l : lit).
A clause is true.
fwd chain is an interpretation transformer that takes a program and
a current interpretation and extends it with the new deducible facts.
Definition fwd_chain cl i := if all (mem i) (body cl) then (head cl) |: i else i.
Lemma fwd_chain_model cl i : fwd_chain cl i = i → clause_true cl i.
Proof.
rewrite /fwd_chain /clause_true; case: ifP ⇒ // _.
by move/setUidPr; rewrite sub1set.
Qed.
Lemma model_fwd_chain cl m : clause_true cl m → fwd_chain cl m = m.
Proof.
rewrite /fwd_chain /clause_true; case: ifP ⇒ //= _ h.
by apply/setUidPr; rewrite sub1set.
Qed.
Lemma fwd_chainP cl m : reflect (fwd_chain cl m = m) (clause_true cl m).
Proof. by apply/(iffP idP);[exact: model_fwd_chain|exact: fwd_chain_model]. Qed.
Lemma fwd_chain_model cl i : fwd_chain cl i = i → clause_true cl i.
Proof.
rewrite /fwd_chain /clause_true; case: ifP ⇒ // _.
by move/setUidPr; rewrite sub1set.
Qed.
Lemma model_fwd_chain cl m : clause_true cl m → fwd_chain cl m = m.
Proof.
rewrite /fwd_chain /clause_true; case: ifP ⇒ //= _ h.
by apply/setUidPr; rewrite sub1set.
Qed.
Lemma fwd_chainP cl m : reflect (fwd_chain cl m = m) (clause_true cl m).
Proof. by apply/(iffP idP);[exact: model_fwd_chain|exact: fwd_chain_model]. Qed.
if the program p has a model m
then fwd_chain has a fixpoint m' s.t m' is a model for p
and a better or equal model than m
Lemma fwd_chain_complete cl m : clause_true cl m →
∃ m', fwd_chain cl m' = m' ∧ clause_true cl m' ∧ m' \subset m.
Proof.
case/minset_exists⇒ m_min /minsetP [? ? ?].
by ∃ m_min; split; [exact: model_fwd_chain|].
Qed.
End Interp.
End MiniLog.
Print Assumptions fwd_chain_complete.
∃ m', fwd_chain cl m' = m' ∧ clause_true cl m' ∧ m' \subset m.
Proof.
case/minset_exists⇒ m_min /minsetP [? ? ?].
by ∃ m_min; split; [exact: model_fwd_chain|].
Qed.
End Interp.
End MiniLog.
Print Assumptions fwd_chain_complete.
Exercise: Add variables