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.

inductive Waffle.Answer :

An enumerated type for the answers to a yes/no question.

Instances For

    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.

    Instances For

      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 --
      #reduce disagree -- fun x => Answer.rec Answer.yes Answer.maybe x
      #check Answer.rec -- {motive : Answer → Sort u_1} → motive Answer.yes → motive → 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 AnswerString we take as motive fun _ => String. In this case we see the type of the recursor is simplified.

      #check Answer.rec (motive := fun _ => String) -- StringStringStringAnswerString

      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.

      A structure for answers with explanations

      Instances For

        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 -- AnswerBool 
        #check Answer.explanation -- AnswerString
        #check -- BoolStringAnswer
        #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.

        "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.

        An example of an answer with nested explanations

        Instances For

          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" ( -/

          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.

          Instances For

            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) -- StringStringString → (String → AnswerString → String) → AnswerString

            The recursor has type StringStringString → (String → AnswerString → String) → AnswerString. 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 AnswerAnswer → Prop. The first two constructors are Answer.le.no_le and Answer.le.le_yes giving the propositions ≤ ans and ans ≤ Answer.yes for any ans. The constructor Answer.le.refl gives the proposition ans ≤ ans for any ans.

            Instances For

              An instance associating to Answer.le

              theorem Waffle.le_trans (a : Waffle.Answer) (b : Waffle.Answer) (c : Waffle.Answer) :
              a bb ca c

              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.

              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 AnswerBool by cases. This gives an error message, as the recursor for Answer is not defined for general types.

                def generous : AnswerBool
                  | Answer.yes => true
                  | => 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
                h_3: Unit → motive Answer.maybe
                ⊢ motive x✝
                 after processing
                the dependent pattern matcher can solve the following kinds of equations
                - <var> = <term> and <term> = <var>
                - <term> = <term> where the terms are definitionally equal
                - <constructor> = <constructor>, 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
                  (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.