First steps with Coq

Assia Mahboubi

Functions, Higher-Order Functions

Functions are defined by binding, abstracting their argument in the body of their definition.
Note: we will make more precise later how type 'nat' is defined, and what the infix '+' refers to. Moreover, typing constraints are enforced : in Coq one can only handle well-typed terms. 'About' is an important query command for defined constants (only)
'Check' gives the type of an arbitrarily complex term, or hints about the reasons why the term is not well-typed.
Functions can be higher-order, i.e., take arguments which themselves have an arrow type:
Note that type annotations are optional when they can be inferred from other typing constraints. Handle with care... 'iter_twice' is a program (and so is plus_five), which can be evaluated by a reduction machine which performs the expected unfolding of definitions, and substitutions.
But something more than just unfolding and substitution happened here, let us see what.

Data Structures, Programs

Enumerated types: colors and booleans

Our first definition of a data structure, as an Inductive type:
  • 'color' is the name we chose for the type we defined
  • 'Type' is a type annotation
  • 'red', 'green' 'blue' are the (only) inhabitants of type 'color', also called constructors.
A program operating on 'color's, by case analysis, also called pattern matching.
Pattern matching defines rewrite rules, which can be used by a reduction machine for evaluating programs
Booleans are defined as an enumerated inductive type, exactly as color.
But it has a syntactic sugar for pattern matching
And infix notations for boolean operations.

Recursive inductive types: natural numbers

  • 'nat' is the name we chose for the type we defined (pretty-printed by my UI)
  • 'Set' is a type annotation
  • 'O', 'S' are the constructors, but only 'O' is an inhabitant.
The (closed) inhabitants of type nat are the smallest collection of terms containing O and closed under S.
A program operating on 'nat' by case analysis, also called pattern matching.
A recursive program, defined by case analysis and recursive call
addn is isomorphic to the program hidden behind the infix plus used in the first example
Here as well, the rewrite rules corresponding to each branch of the pattern matching are used to reduce programs
We are now ready to implement boolean predicates on nat, like comparison. Here is a convenient syntax for nested case analysis.
Note the output message : Coq has checked that leq is terminating Riddle: what is this?

Polymorphic Datatypes: Containers

A polymorphic type is a type itself parameterized by a type. One of the simplest examples: an option type
In fact, 'option' has an arrow type (sort)
Now a type whose inhabitants are pairs of natural numbers
The constructor is building a pair
Pattern matching destructs a pair
We can design a polymorphic version of nat_pair, with a star infix notation.
Polymorphic lists, in a name space:
Remark : this nature of polymorphism triggers a fair amount of verbosity.
Type inference at work
One can even do better and declare the polymorphic arguments as implicits: then they will no more be provided. This is what the standard library does.
Remark : this can be seen as a polymorphic variant of type nat... Exercise: how to implement a function extracting the head of a list? Caveat: Coq functions have to be total ... First solution: use option
pros: captures the exceptional case. cons: contaminating monadic style. Second solution : use an exceptional value
pros: better compositionality. cons: requires an inhabited type, risks of confusion.

Statements and Proofs.

Trivial proofs, by computation

Polymorphic equality, with an infix notation '='
Check checks that a statement is well-formed
But of course not that it is provable :)
Stating and proving our first (ground) identity, interactively.
The proof is trivial, because both hand-sides are convertible, that is equal modulo "computation" Proofs by conversion also hold for non-ground terms: here is a statement with parameters, proved by conversion.
In order to go beyond the known rewrite rules, case analysis can be needed.
and now the proof is by conversion in both cases.
As we shall see, conversion can be used with profit to automate proofs and absorb bureaucracy.

Case analysis, recurrence, induction

Now an equivalence statement between two boolean predicates
We need a case analysis on the natural numbers.
Let's do the easy case first.
Here as well we can improve the script... We need to reason by recurrence (induction) on m

Applying a lemma

How to use lemma leqnn in a proof step.
Note that I have used a "coercion" here, which allows me to ommit the _ = true
finds the value of parameters
conversion took care of aligning a and 0 + a

Propositions as types, proofs as programs

This is our first implication, a simple arrow.
We can define proof terms using definitions
We can define functions in interactive mode
Statements with parameter variables of type nat, bool, etc. are types depending on these variables. They are called dependent products.
on paper, ∀ is sometimes also denoted Π: Π m : nat, Π n : nat, (m * n == 0) = (m == 0) || (n == 0). muln_eq0 is a type of functions taking two arguments and computing a proof of the corresponding instance of the statement
The 'apply' tactic juste computes the appropriate arguments from the goal. This correspondence goes a long way:
  • conjunctions are stated as types of pairs, whose proofs are pairs of proofs,
  • disjunctions are stated as sum types, whose proofs are either a proof of one branch or a proof of the other branch.
  • proofs of existential statements are a pair of a witness and a proof, etc.
As a result, Coq's type theory and the corresponding programming language provides alone the rules of the logic, and the syntax of proofs. Proof checking is type checking.

Back to proofs by induction

Mind the messages output by Coq
In Coq, the primitive concept is in fact the 'match' and 'fix' terms
But `nat_ind` is what the 'elim' tactic uses
Guessing P is too hard for apply' s heuristic
and this argument is what is guessed by the elim tactic, which moreover has intro pattern facilities.


Proofs by computational reflection

We assume op2 x z = op2 z x = x for any x
A useful auxiliary data structure: abstract syntax trees
Evaluation of an expr to a term of type dom:
Normalization, i.e. ereasing occurrences of z
Correctness theorem, relies on the assumptions on op2 and z and u. This version provides a semi-decision procedure in dom.
If an oracle guesses e1 and e2, we can prove this identity "by computation"
We cannot implement such an oracle as a Coq function, but we could use, e.g. the Ltac metalanguage available at top-level. The latter example is a baby ring tactic. Here is the real one:

Proofs by type inference

boring and fragile: calls for an automation tactic...
Let us try a different approach, based on so-called sigma-types
- Inhabitants of even_nat are pairs of a natural number n, and a proof that it is even. - EvenNat is the constructor of this (inductive) type - val is the first projection, onto the natural number - even_val is the second projection, onto the proof. Using our theory of even, we can build elements of even_nat
Our "Canonical" declarations have added hints to the unification algorithm, which provide canonical solutions to otherwise unsolvable unification problems. See also "Canonical Structures for the Working Coq User" AM and E. Tassi