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):

  : 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)

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

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


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 to even (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)

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.

\cfrac{Γ ⊢ e: eq \, A \, t \, u \qquad Γ, y: A, e': eq \, A \, t \, y ⊢ C(y, e'): s \qquad Γ ⊢ f: C(t, eq\_refl_{A, t})}{ Γ ⊢ \texttt{match e as e' in eq_y return C(y, e') with eq_refl => f end}: C(u,e) }

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=y
  • discriminate (constructor discrimination)
    • if you have an equality between two different constructors C(t_1, …, t_n) = D(u_1, …, u_n), you can deduce $⊥$
  • 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$
  • inversion (necessary conditions): opposite of the the inroduction rule
    • from even (S(S n)), you can generate the hypothesis even n
  • 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: positive
  • I -> 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 vs destruct: case is really primitive, it’s only the match construction of the CIC (ex: case (n:nat) for P will create a new goal nat -> P' (nat not introduced in the hypotheses)).

  • fix: even more primitive

    if you have a goal

      nat -> nat -> P

    and use fix 1 (1 = on the first argument, Coq will check that you use a strictly lower nat):

      H: nat -> nat -> P
      nat -> nat -> P

    (the first nat has to be strictly lower)

Leave a comment