Lecture 3: Inductive Types
Teacher: Bruno Barras
Inductive nat : Type := O : nat | S : nat->nat.
Inductive bool := true | false.
Inductive list (A:Type) : Type :=
nil | cons (hd:A) (tl:list A).
(* cons : forall A:Type, A -> list A -> list A *)
Inductive tree (A:Type) :=
leaf | node (_:A) (_:nat->tree A).
Recursor (seen in Gödel’s System T):
nat_rect
: forall P:nat->Type,
P O -> (forall n, P n -> P (S n)) ->
forall n, P n.
:= fun P h0 hS => fix F n :=
match n return P n with
| O => h0
| S k => hS k (F k)
end
In Coq, this is not primitive: it is decomposed into
- a pattern-matching
- a guarded (Coq checks that it’s well-founded (total definition)) fixpoint
NB: the return
is necessary to guess the type of the branches (it’s a unification problem that is not decidable and has no unique solution otherwise)
Logical connectives:
Inductive True : Prop := I.
True_rect : forall P:Type, P -> True -> P.
Inductive False : Prop := .
False_rect : forall P:Type, False -> P
Inductive and (A B:Prop) : Prop :=
conj (_:A) (_:B).
and_rect : forall (A B:Prop) (P:Type), (A->B->P)-> A/\B
-> P
Inductive or (A B:Prop) : Prop :=
or_introl (_:A) | or_intror (_:B).
or_ind : forall (A B P:Prop), (A->P) -> (B->P) -> A\/B -> P.
Mutual and nested inductive types
Introduction of several types simultaneously (referring to one another), e.g. finitely branching trees:
Inductive tree (A:Type) :=
| node (_:A) (_:forest A)
with forest (A:Type) :=
| nil
| next (_:tree A) (_:forest A).
Elimination: pattern mathching as usual, or mutual fix, e.g.
fix size (t:tree A) : nat :=
match t with node _ f => S(sizef f) end
with sizef (f:forest A) : nat :=
match f with
| nil => 0
| next t f’ => size t + sizef f’
end
The scheme automatically generated by Coq sees trees as a non-inductive type, forest as an inductive type referring only to itself. The command Scheme
generates the mutually inductive scheme.
Nested inductive types
Example, instead of using forest
mutually as before:
Inductive tree (A:Type) :=
| node (_:A) (_:list (tree A)).
Nested: tree
appears as an argument of the inductive type list
.
Pros: we can take advantage of the standard library for list
, and not reinvent the wheel.
For each nested inductive type, one can define an equivalent mutual inductive type (so nested inductive don’t add anything to mutual types). But the converse is true as well.
You can’t use mutual and nested types at the same time: use nested (resp. mutual) fixpoints instead for nested (resp. mutual) inductive types.
Predicate defined by inference rules
Example:
Inductive even (n:nat) : Prop :=
even_i (half:nat) (_:half+half=n).
but you may want to define even
by its usual inference rules. In which case even (S(S n))
depends on even n
… and ultimately on even 0
.
Inductive Families: indexed type, all the $P(n)$’s are defined simultaneously, but
- Constructors don’t inhabit uniformly the members of the family (e.g. only even numbers)
- Recursive arguments can change the value of the index (e.g. from
even n
toeven (S(S n))
)
Inductive even : nat -> Prop :=
E0 : even O
| ESS (n:nat) (e:even n) : even (S (S n)).
Here, it’s not a parametric definition. Example of a parametric one:
Inductive and (A B:Prop) : Prop :=
conj (_:A) (_:B).
⟶ conj
is defined for all values of A
and B
Whereas here, even n
is defined only for the even n
’s.
Resulting dependent scheme (most general scheme that can be defined on this family):
forall (P : forall n, even n -> Prop),
P 0 E0 ->
(forall n (e:even n), P n e -> P (S (S n)) (ESS n e)) ->
forall n (e:even n), P n e
NB: Coq don’t automatically generates this most general scheme, but the Scheme
command can do it:
Scheme Induction
: the one above-
Scheme Minimality
: the one produced by Coq by default:even_ind : forall (P:nat->Prop), P O -> (forall n, P n -> P (S (S n))) -> forall n, even n -> P n.
More complex return
clause:
Definition even_ind_dep (P:forall n , even n -> Prop)
(h0:P 0 E0)
(hSS:forall n e, P n e -> P (S (S n)) (ESS n e))
: forall n, even n -> P n :=
fix F n e :=
match e as e’ in even k return P k e’ with | E0 => h0 : P 0 E0
| ESS k e’ =>
hSS k e’ (F k e’) : P (S (S k)) (ESS k e’)
end
The most “canonical” inductive family: equality
All the other inductive families can be generated out of this one:
Inductive eq (A : Type) (a : A) : A -> Prop :=
eq_refl : eq A a a.
Notation "x = y" := (eq x y).
NB: in eq A a a
: the first two arguments are parameters, the last one is an index.
Non-dependent version (automatically generated by Coq):
\[\cfrac{Γ ⊢ e: eq \, A \, t \, u \qquad Γ, y: A ⊢ C: s \qquad Γ ⊢ f: C(t)}{ Γ ⊢ \texttt{match ... end}: C(u) }\]Equality-related Tactics:
f_equal
(congruence) x=ydiscriminate
(constructor discrimination)- if you have an equality between two different constructors
C(t_1, …, t_n) = D(u_1, …, u_n)
, you can deduce $⊥$
- if you have an equality between two different constructors
injection
(injectivity of constructors)- if you have an equality with the same constructor
C(t_1, …, t_n) = C(u_1, …, u_n)
, you can deduce $t_i = u_i \; ∀ i$
- if you have an equality with the same constructor
inversion
(necessary conditions): opposite of the the inroduction rule- from
even (S(S n))
, you can generate the hypothesiseven n
- from
rewrite
(substitution)symmetry
,transitivity
Inductive types with parameters and index
Classical example: vectors:
Inductive vect (A:Type) : nat -> Type :=
| niln : vect A O
| consn :
A -> forall n:nat, vect A n -> vect A (S n).
Family of types-predicates: $Γ⊢vect :Type→nat →Type$
Arguments corresponding to parametes: you can’t patter-match upon them: cf. the last TP: instead of | cons A x l =>
, | cons _ x l =>
.
Non-uniform parameters (advanced notion)
Non-uniform parameter (exist in CamL as well):
- Like parameters for pattern-matching: uniform conclusion
- Like indices for recurrence: value can change in recursive subterms
Example: complete binary trees:
Inductive tuple (A:Type) :=
| H0 (_:A)
| HS (_:tuple (A*A)).
Definition t4 : tuple nat :=
HS nat (HS (nat*nat) (H0 _ ((1,2),(3,4))).
NB: with non-uniform parameters, one can encode all inductive families thanks to equality
Inductive even (n:nat) : Prop :=
E0’ (_:n=0)
| ESS’ (k:nat) (e:even k) (_:n=S (S k)).
Definition E0 : even 0 := E0’ 0 eq_refl.
Definition ESS n e : even (S (S n)) :=
ESS’ (S (S n)) n e eq_refl.
Definition of inductive types
NB: you can’t define any inductive type: ex:
Inductive lambda : Type :=
| Lam : (lambda -> lambda) -> lambda
⟶ lambda
must appear positively in each argument of each constructor (which is not the case for the first lambda
here), for the fixpoint theorem.
Otherwise, we could define:
Definition app (x y:lambda)
:= match x with (Lam f) => f y end.
Definition Delta := Lam (fun x => app x x).
Definition Omega := app Delta Delta.
and even worse:
Fixpoint lambda_to_nat (t : lambda) : nat :=
match t with Lam f -> S (lambda_to_nat (f t)) end.
then (lambda_to_nat (Lam (fun x => x)))
reduces to its successor (impossible in Peano arithmetic)! ⟹ Inconsistency
Positivity/Negativity of I
:
I
: positiveI -> A
: negative(I -> A ) -> A
: positive
But positivity is not enought to define an inductive type: it must be strictly positive: I
can never be used on the left of a ->
in the arguments (i.e. each argument is of the form C_1 -> ⋯ -> C_n
or C_1 -> ⋯ -> C_n -> I
, where the C_i
’s don’t include I
). So that I
is strictly positive, but (I -> A ) -> A
is not.
Tactics for case analysis
-
case
vsdestruct
:case
is really primitive, it’s only thematch
construction of the CIC (ex:case (n:nat)
forP
will create a new goalnat -> P'
(nat
not introduced in the hypotheses)). -
fix
: even more primitiveif you have a goal
---------------------- nat -> nat -> P
and use
fix 1
(1
= on the first argument, Coq will check that you use a strictly lowernat
):H: nat -> nat -> P ---------------------- nat -> nat -> P
(the first
nat
has to be strictly lower)
Leave a comment