Formal Software Verification

6.6. Building New Datatypes🔗

The same schema builds richer types. A binary tree is either a leaf or a branch carrying a value and two subtrees, and functions on it recurse on the subtrees. The section defines size, height, and mirror and states their laws, proving only the closed instances by computation; the general laws are Lecture 7's, since they need induction.

namespace Func inductive Tree (α : Type) where | leaf | branch (l : Tree α) (x : α) (r : Tree α) def treeSize {α : Type} : Tree α | .leaf => 0 | .branch l _ r => treeSize l + 1 + treeSize r def height {α : Type} : Tree α | .leaf => 0 | .branch l _ r => max (height l) (height r) + 1 def mirror {α : Type} : Tree α Tree α | .leaf => .leaf | .branch l x r => .branch (mirror r) x (mirror l) end Func

The recursor of Tree shows the general schema on a fresh type. It takes a value for the leaf case and, for the branch case, a function that receives the two subtrees, the stored value, and the recursive results on the two subtrees, which become the induction hypotheses of a proof by induction.

namespace Func @Tree.rec : {α : Type} {motive : Tree α Sort u_1} motive Tree.leaf ((l : Tree α) (x : α) (r : Tree α) motive l motive r motive (l.branch x r)) (t : Tree α) motive t#check @Tree.rec end Func
@Tree.rec : {α : Type} 
  {motive : Tree α  Sort u_1} 
    motive Tree.leaf 
      ((l : Tree α)  (x : α)  (r : Tree α)  motive l  motive r  motive (l.branch x r))  (t : Tree α)  motive t

The general laws this section states and Lecture 7 proves are mirror (mirror t) = t, treeSize (mirror t) = treeSize t, and the counting law relating the leaves and the branches of a tree. Each needs induction, so this section proves only their closed instances.

6.6.1. Examples🔗

The examples below build a tree, compute with it, and read the other datatypes the schema produces.

Example 1. A small tree with one value at the root and one in its left subtree.

namespace Func def t1 : Tree := .branch (.branch .leaf 1 .leaf) 2 .leaf end Func

Example 2. size counts the branches, recursing on both subtrees.

namespace Func 2#eval treeSize t1 end Func
2

Example 3. height takes the greater of the two subtree heights and adds one.

namespace Func 2#eval height t1 end Func
2

Example 4. mirror swaps the two subtrees at every branch, and this constructor law holds for every tree by computation, with no induction.

namespace Func example {α : Type} (l : Tree α) (x : α) (r : Tree α) : mirror (.branch l x r) = .branch (mirror r) x (mirror l) := rfl end Func

The doubly-mirrored law mirror (mirror t) = t, for every tree, is different, since it needs induction, and it is a worked example of Lecture 7.

Example 5. Mirroring a leaf changes nothing.

namespace Func example : mirror (Tree.leaf : Tree ) = Tree.leaf := rfl end Func

Example 6. Mirroring preserves the size, here on the closed tree; the general law waits for Lecture 7.

namespace Func example : treeSize (mirror t1) = treeSize t1 := rfl end Func

Example 7. A sum type α ⊕ β holds a value from one side or the other, and a match on inl/inr consumes it.

namespace Func def fromSum : Bool | .inl n => n | .inr b => if b then 1 else 0 example : fromSum (.inl 4) = 4 := rfl end Func

Example 8. Option is the canonical nullable type, and a function may map over its value. The law for some holds for every function and argument by computation, with no induction.

namespace Func def mapOption {α β : Type} (f : α β) : Option α Option β | none => none | some a => some (f a) example {α β : Type} (f : α β) (a : α) : mapOption f (some a) = some (f a) := rfl end Func

Example 9. A dependent inductive type carries information in its own type. A Vec α n is a list of length n, and its constructors record the length. This is a read-only preview; the later weeks develop dependent types.

namespace Func inductive Vec (α : Type) : Type where | nil : Vec α 0 | cons {n : } : α Vec α n Vec α (n + 1) end Func

Example 10. Because the type of vhead demands a nonempty vector, the empty case cannot arise, and the function is total without an option.

namespace Func def vhead {α : Type} {n : } : Vec α (n + 1) α | .cons x _ => x def v1 : Vec 2 := .cons 3 (.cons 4 .nil) 3#eval vhead v1 end Func
3