Lecture 17 Recursive Data Types

Joseph Haugh

University of New Mexico

Free Recall

Review

  • What is a higher order function?
  • A function which takes a function as an argument or returns a function
  • What is a predicate?
  • A function which returns Bool
  • What is a list?
  • A list of a is either empty or cons of an a and a list of a

Recursive Data Types

As we saw before a type declaration/synonym is not powerful enough to express a recursive type such as

type Tree = (Int, [Tree])

However data declarations are!

Recursive Data Types

Possibly the simplest example is the peano numbers:

data Nat = Zero | Succ Nat

A Nat is either Zero or the Succ of a Nat.

Recursive Data Types

data Nat = Zero | Succ Nat

Let’s define a function which transforms a Nat into an Int:

nat2Int :: Nat -> Int
nat2Int Zero     = 0
nat2Int (Succ n) = 1 + nat2Int n

Can we go the other way?

int2Nat :: Int -> Nat
int2Nat 0 = Zero
int2Nat n = Succ (int2Nat (n-1))

Recursive Data Types

data Nat = Zero | Succ Nat

Could we use these functions to define addition over Nats?

add :: Nat -> Nat -> Nat
add m n = int2Nat (nat2Int m + nat2Int n)

Hmm works but it’s ugly! Could we define it without relying on these functions?

Exercise: add

Define the add function from the previous slide but do it directly without the use of int2Nat and nat2Int.

data Nat = Zero | Succ Nat

add :: Nat -> Nat -> Nat

Exercise: add

Define the add function from the previous slide but do it directly without the use of int2Nat and nat2Int.

data Nat = Zero | Succ Nat

add :: Nat -> Nat -> Nat
add Zero    n  = n
add (Succ m) n = Succ (add m n)

Exercise: add

data Nat = Zero | Succ Nat

add :: Nat -> Nat -> Nat
add Zero    n  = n
add (Succ m) n = Succ (add m n)
add (Succ (Succ Zero)) (Succ Zero)
| { applying add }
Succ (add (Succ Zero) (Succ Zero))
| { applying add }
Succ (Succ (add Zero (Succ Zero)))
| { applying add }
Succ (Succ (Succ Zero))

Does this remind of you of another function we have defined?

add vs append

data Nat = Zero | Succ Nat

add :: Nat -> Nat -> Nat
add Zero    n  = n
add (Succ m) n = Succ (add m n)

append :: [a] -> [a] -> [a]
append []     ys = ys
append (x:xs) ys = x : append xs ys

Defining List

Recall in the review how I described a list:

A list of a is either empty or cons of an element and a list of a

How can we turn this into a concrete Haskell data type?

Exercise: Define A List Data Type

Try to define a data type which represents a list with the following description:

A list of a is either empty or cons of an a and a list of a

data List a = Nil | Cons a (List a)

List As A Data Type

data List a = Nil | Cons a (List a)

What type does Nil have?

nil :: List a

What type does [] have?

[] :: [a]

What type does Cons have?

Cons :: a -> List a -> List a

What type does (:) have?

(:) :: a -> [a] -> [a]

Redefine: Length

Let’s redefine our old friend length using our new List datatype:

data List a = Nil | Cons a (List a)

length' :: List a -> Int
length' Nil         = 0
length' (Cons _ xs) = 1 + length' xs

Redefine: Sum

data List a = Nil | Cons a (List a)

sum' :: List Int -> Int
sum' Nil         = 0
sum' (Cons x xs) = x + sum' xs

Exercise: Redefine foldr

Redefine the foldr function to work over the List data type:

data List a = Nil | Cons a (List a)

foldrList :: (a -> b -> b) -> b -> List a -> b

Exercise: Redefine foldr

Redefine the foldr function to work over the List data type:

data List a = Nil | Cons a (List a)

foldrList :: (a -> b -> b) -> b -> List a -> b
foldrList _ z Nil         = z
foldrList f z (Cons x xs) = f x (foldrList f z xs)

Redefine foldl

data List a = Nil | Cons a (List a)

foldlList :: (b -> a -> b) -> b -> List a -> b
foldlList _ z Nil         = z
foldlList f z (Cons x xs) = foldlList f (f z x) xs

Does the right hand side differ from the normal foldl?

foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z []     = z
foldl f z (x:xs) = foldl f (f z x) xs

Exercise: Tree Data Type

Define a data type which represents a Tree with the following description:

A Tree of a is either a Leaf of a or a Node of Tree of a, an a, and another Tree of a

data Tree a = Leaf a | Node (Tree a) a (Tree a)

Here is an example Tree

t :: Tree Int
t = Node (Node (Leaf 1) 3 (Leaf 4)) 5
         (Node (Leaf 6) 7 (Leaf 9))

Trees: Occurs

Let’s write a function, occurs, which like elem for lists search for a given element in a Tree

data Tree a = Leaf a | Node (Tree a) a (Tree a)

occurs :: Eq a => a -> Tree a -> Bool
occurs x (Leaf y)     = x == y
occurs x (Node l y r) = x == y || occurs x l || occurs x r

Exercise: flatten

Write a function, flatten, which transforms a Tree into a list in depth first order.

data Tree a = Leaf a | Node (Tree a) a (Tree a)

flatten :: Tree a -> [a]

Exercise: flatten

Write a function, flatten, which transforms a Tree into a list in depth first order.

data Tree a = Leaf a | Node (Tree a) a (Tree a)

flatten :: Tree a -> [a]
flatten (Leaf x)     = [x]
flatten (Node l x r) = flatten l ++ [x] ++ flatten r

Exercise: occursST

What if our Tree was a search Tree? Meaning that if a value exists in the Tree and it is less than the current node’s value then it must be in the left subtree, if it is greater then it must be in the right subtree. Given that your argument has this form redefine occurs:

occursST :: Ord a => a -> Tree a -> Bool

Exercise: occursST

What if our Tree was a search Tree? Meaning that if a value exists in the Tree and it is less than the current node’s value then it must be in the left subtree, if it is greater then it must be in the right subtree. Given that your argument has this form redefine occurs:

occursST :: Ord a => a -> Tree a -> Bool
occursST x (Leaf y) = x == y
occursST x (Node l y r)
    | x == y    = True
    | x <  y    = occursST x l
    | otherwise = occursST x r

Why Isn’t This Builtin?

We can define many different Tree types based on our needs, for example:

data Tree a = Leaf a | Node (Tree a) (Tree a)

data Tree a = Leaf | Node (Tree a) a (Tree a)

data Tree a b = Leaf a | Node (Tree a b) b (Tree a b)

data Tree a = Node a [Tree a]