Inductive Types #
The main way of introducing new types in Lean is through inductive types. An inductive type is a type
which is specified by saying how to construct terms of that type. Concretely, we specify the types of
a set of constructors
all of which have final codomain the type being constructed.
When an inductive type is defined, Lean automatically generates a recursor for that type, which allows defining by cases, matching, and induction on terms of that type.
Enumerated Types #
Enumerated types are a special case of inductive types where the constructors have no arguments. We simply list the terms of that type.
- yes: Waffle.Answer
- no: Waffle.Answer
- maybe: Waffle.Answer
An enumerated type for the answers to a yes/no question.
Instances For
Equations
- Waffle.instReprAnswer = { reprPrec := Waffle.reprAnswer✝ }
Equations
- Waffle.instInhabitedAnswer = { default := Waffle.Answer.yes }
Equations
- Waffle.instDecidableEqAnswer x y = if h : Waffle.Answer.toCtorIdx x = Waffle.Answer.toCtorIdx y then isTrue (_ : x = y) else isFalse (_ : x = y → False)
Some terms introduced when an inductive type is defined. The last two depended on the classes Repr
and Inhabited
being derived for the type.
#check Answer -- Type
#check Answer.yes -- Answer
#eval Answer.yes -- Waffle.Answer.yes
#eval (default : Answer) -- Waffle.Answer.yes
A function defined by cases which disagrees with the given answer.
Equations
- Waffle.disagree x = match x with | Waffle.Answer.yes => Waffle.Answer.no | Waffle.Answer.no => Waffle.Answer.yes | Waffle.Answer.maybe => Waffle.Answer.maybe
The disagree
function is defined by cases on the argument. The definition is rewritten by Lean in terms of the rec
function, which is automatically generated by Lean for each inductive type. The rec
function is a recursor for the type, which allows defining by cases, matching, and induction on terms of that type.
#eval disagree Answer.yes -- Waffle.Answer.no
#reduce disagree -- fun x => Answer.rec Answer.no Answer.yes Answer.maybe x
#check Answer.rec -- {motive : Answer → Sort u_1} → motive Answer.yes → motive Answer.no → motive Answer.maybe → (t : Answer) → motive t
The type of the recursor looks complicated because we can recursively define dependent functions on the inductive type answer, i.e., a function f
which takes an argument of type x: Answer
and returns a value whose type depends on x
. The motive
argument specifies the type of f x
for x: Answer
.
Note: You should think of the word motive
as corresponding to the english word motif
, not the english word motive
. The motive
encodes the type of the function we are defining, not the reason for defining it.
Recursive definitions simplify if we are defining a (non-dependent) function on the type. Here the motive is a constant function. For example, to define a function Answer → String
we take as motive fun _ => String
.
In this case we see the type of the recursor is simplified.
#check Answer.rec (motive := fun _ => String) -- String → String → String → Answer → String
Indeed the above is easy to interpret. The recursor takes as input the value of the function being defined on the three constructors and returns a function from Answer
to String
.
If an answer is equal to its disagreement, then it must be maybe
.
Structures #
Another simple kind of inductive type is a Structure
, whose terms correspond to values of specified fields of given types.
An element of the type can be constructed using special syntax which is similar to a tuple but with left and right angle brackets instead of parentheses.
example : Answer := ⟨true, "I agree"⟩
When a structure is defined, a constructor named mk
is automatically generated. The constructor is a function which takes the arguments of the structure in the order they are declared and returns a term of the structure type.
Further, projections onto each of the fields are defined as functions named after the field. As with any induction type, a recursor is automatically generated for the structure.
#check Answer -- Type
#check Answer.agree -- Answer → Bool
#check Answer.explanation -- Answer → String
#check Answer.mk -- Bool → String → Answer
#check Answer.rec -- {motive : Answer → Sort u} → ((agree : Bool) → (explanation : String) → motive { agree := agree, explanation := explanation }) → (t : Answer) → motive t
We can rewrite the structure Answer
in terms of inductive types. In this case we have to define the projections manually.
- mk: Bool → String → VerboseExplained.Answer
Instances For
Equations
- VerboseExplained.Answer.agree x = match x with | VerboseExplained.Answer.mk agree a => agree
Equations
- VerboseExplained.Answer.explanation x = match x with | VerboseExplained.Answer.mk a exp => exp
"Genuine" Inductive Types #
Our earlier examples had two degenerate forms of inductive types. The main point of inductive types is that terms of such a type can be formed using terms of the same type recursively. For example, we can construct an Answer
type which includes a constructor that adds an explanation to an existing answer. Note that the existing answer itself can be an Answer
with an explanation.
- yes: LongWinded.Answer
- no: LongWinded.Answer
- maybe: LongWinded.Answer
- explained: String → LongWinded.Answer → LongWinded.Answer
Instances For
Equations
- LongWinded.instInhabitedAnswer = { default := LongWinded.Answer.yes }
Equations
- LongWinded.instReprAnswer = { reprPrec := LongWinded.reprAnswer✝ }
An example of an answer
Equations
An example of an answer with nested explanations
Equations
- LongWinded.egAnswerLong = LongWinded.Answer.explained "Let me play the devil's advocate" (LongWinded.Answer.explained "This is silly" LongWinded.Answer.no)
Another example, to illustrate notation
Equations
Here is an example of an answer with nested explanations.
#eval egAnswerLong /- LongWinded.Answer.explained
"Let me play the devil's advocate"
(LongWinded.Answer.explained "This is silly" (LongWinded.Answer.no)) -/
A function that disagrees with an answer. The explanation is ridiculed.
Equations
- LongWinded.disagree LongWinded.Answer.yes = LongWinded.Answer.no
- LongWinded.disagree LongWinded.Answer.no = LongWinded.Answer.yes
- LongWinded.disagree LongWinded.Answer.maybe = LongWinded.Answer.maybe
- LongWinded.disagree (LongWinded.Answer.explained a a_1) = LongWinded.Answer.explained ("It would be silly to say `" ++ a ++ "'") (LongWinded.disagree a_1)
We can again define a function that disagrees with an answer by cases. In this case the definition is recursive, so the function in the Answer.explained exp ans
case depends on the recursive call to disagree
on ans
.
#eval disagree egAnswerLong /- LongWinded.Answer.explained
"It would be silly to say `Let me play the devil's advocate'"
(LongWinded.Answer.explained "It would be silly to say `This is silly'" (LongWinded.Answer.yes)) -/
A string explanation from the answer, reversing the concatenation of all explanations and adding one based on the answer.
Equations
- LongWinded.explanation LongWinded.Answer.yes = "I agree"
- LongWinded.explanation LongWinded.Answer.no = "I disagree"
- LongWinded.explanation LongWinded.Answer.maybe = "I don't know"
- LongWinded.explanation (LongWinded.Answer.explained a a_1) = LongWinded.explanation a_1 ++ " because " ++ a
We can similarly define a function that gives a string explanation from the answer, reversing the concatenation of all explanations and adding one based on the answer.
#eval explanation egAnswerLong -- "I disagree because This is silly because Let me play the devil's advocate"
The recursor in such a case has to enable recursive definitions. To see how this happnes, we can check the type of the recursor. We consider the case where we are defining a function from Answer
to String
.
#check @Answer.rec (motive := fun _ => String) -- String → String → String → (String → Answer → String → String) → Answer → String
The recursor has type String → String → String → (String → Answer → String → String) → Answer → String
. The first three arguments are the values of the function for the three constructors without an explanation. The last argument is a function that takes the value of the function for the explanation case, the answer, and the string explanation of the answer, and returns the value of the function for the answer. The last argument is the answer itself.
Indexed Inductive Types #
A more complex variant of inductive types is an indexed inductive type. Here we introduce a family of inductive types with the constructors giving terms having types lying in the family. As an example, we define an indexed inductive type capturing ≤
on the type Waffle.Answer
.
As this gives a proposition for a pair of answers, the family has type Answer → Answer → Prop
. The first two constructors are Answer.le.no_le
and Answer.le.le_yes
giving the propositions Answer.no ≤ ans
and ans ≤ Answer.yes
for any ans
. The constructor Answer.le.refl
gives the proposition ans ≤ ans
for any ans
.
- no_le: ∀ (ans : Waffle.Answer), Waffle.Answer.le Waffle.Answer.no ans
- le_yes: ∀ (ans : Waffle.Answer), Waffle.Answer.le ans Waffle.Answer.yes
- refl: ∀ (ans : Waffle.Answer), Waffle.Answer.le ans ans
Instances For
An instance associating ≤
to Answer.le
Equations
- Waffle.instLEAnswer = { le := Waffle.Answer.le }
transitivity of the relation ≤
on answers
Prop
versus Type
#
To illustrate the difference between propositions and general types, we define a variant of the Answer
type in the Prop
universe. The constructors are the same as for Waffle.Answer
, but the type is Prop
instead of Type
.
- yes: PropAnswer.Answer
- no: PropAnswer.Answer
- maybe: PropAnswer.Answer
Answer
as an inductive prop
Instances For
We see that what we have defined is a proposition.
#check Answer -- PropAnswer.Answer : Prop
We can try to define a function Answer → Bool
by cases. This gives an error message, as the recursor for Answer
is not defined for general types.
def generous : Answer → Bool
| Answer.yes => true
| Answer.no => false
| Answer.maybe => true
gives error message
tactic 'cases' failed, nested error:
tactic 'induction' failed, recursor 'PropAnswer.Answer.casesOn' can only eliminate into Prop
motive: Answer → Sort ?u.9737
h_1: Unit → motive Answer.yes
h_2: Unit → motive Answer.no
h_3: Unit → motive Answer.maybe
⊢ motive x✝
after processing
_
the dependent pattern matcher can solve the following kinds of equations
- = and =
- = where the terms are definitionally equal
- = , examples: List.cons x xs = List.cons y ys, and List.cons x xs = List.nil
The conceptual reason for this is that two terms of a proposition are definitionally equal. But the definition by cases would give a function that is not constant on the proposition, so equal arguments will give unequal results, a logical contradiction.
The formal reason lies in the type of the recursor constructed for the proposition. The recursor is defined for propositions, not for general types. The type of the recursor is
#check Answer.rec /- PropAnswer.Answer.rec {motive : Answer → Prop} (yes : motive Answer.yes) (no : motive Answer.no)
(maybe : motive Answer.maybe) (t : Answer) : motive t
-/
In particular if we wish to define a term Answer → α
, then the motive must be fun _ ↦ α
and so α
must be a proposition.