Lecture 16 Data Types

Joseph Haugh

University of New Mexico

Free Recall

Review

What is the type of the following expressions?

tail ["ciri", "geralt", "yennefer"] ::

Review

What is the type of the following expressions?

tail ["ciri", "geralt", "yennefer"] :: [String]

tail :: [a] -> [a]
["ciri", "geralt", "yennefer"] :: [String]

Review

What is the type of the following expressions?

map (filter even) [[1,2,3], [4,5,6], [7,8,9]] ::

Review

What is the type of the following expressions?

map (filter even) [[1,2,3], [4,5,6], [7,8,9]] :: [[Int]]

map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
even :: Int -> Bool
[[1,2,3], [4,5,6], [7,8,9]] :: [[Int]]

filter even :: [Int] -> [Int]
map (filter even) :: [[Int]] -> [[Int]]
map (filter even) [[1,2,3], [4,5,6], [7,8,9]]

Review

What is the type of the following expressions?

foldr (++) ::

Review

What is the type of the following expressions?

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

foldr :: (a -> b -> b) -> b -> [a] -> b
(++) :: [a] -> [a] -> [a]

foldr (++)
| { a = [a], b = [a] }
:: [a] -> [[a]] -> [a]

Review

What is the type of the following expressions?

map (foldr (++) []) ::

Review

What is the type of the following expressions?

map (foldr (++) []) :: [[[a]]] -> [[a]]

map :: (a -> b) -> [a] -> [b]
foldr :: (a -> b -> b) -> b -> [a] -> b
(++) :: [a] -> [a] -> [a]
[] :: [a]

foldr (++)
| { a = [a], b = [a] }
:: [a] -> [[a]] -> [a]

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

map (foldr (++) [])
| { a = [[a]], b = [a] }
:: [[[a]]] -> [[a]]

Type Declarations

You can define type declarations or type synonyms as follows:

type String = [Char]

This means that now when you use String in a type signature you really mean [Char].

Type Declarations

The right hand side of the equal can be any valid type:

  • A tuple
type Pos = (Int, Int)
  • A function
type Trans = Pos -> Pos

Type Declaration Arguments

A type declaration can take type level arguments:

type Pair a = (a, a)

type Assoc k v = [(k, v)]

Exercise: Find Function

Write a function find that takes a key and an association list and returns the value associated with the key.

type Assoc k v = [(k, v)]

find :: Eq k => k -> Assoc k v -> v

Exercise: Find Function

Write a function find that takes a key and an association list and returns the value associated with the key.

type Assoc k v = [(k, v)]

find :: Eq k => k -> Assoc k v -> v
find k t = head [ v | (k', v) <- t, k == k' ]

Type Declaration Limitations

A type declaration cannot be recursive:

type Tree = (Int, [Tree])

is not valid Haskell. However, this seems like it could be useful, so how could we do this?

Data Declarations

A data declaration is similar to a type declaration, but instead of simply renaming an existing type you are instead creating a completely novel type.

A builtin example is the Bool type:

data Bool = False | True

You can read the | as or.

A Bool is either False or True.

Data Declarations

data Direction = North | East | South | West

A Direction is either North, East, South, or West.

Data Declarations

We can then write a function to move in a 2d coordinate system based on a direction:

move :: Direction -> Pos -> Pos
move North (x, y) = (x, y+1)
move South (x, y) = (x, y-1)
move East  (x, y) = (x+1, y)
move West  (x, y) = (x-1, y)

Exercise: moves

Write a function moves that takes a list of directions and a starting position and returns the final position after moving in each direction.

moves :: [Direction] -> Pos -> Pos

Exercise: moves

Write a function moves that takes a list of directions and a starting position and returns the final position after moving in each direction.

moves :: [Direction] -> Pos -> Pos
moves []     p = p
moves (d:ds) p = moves ds (move d p)

Exercise: moves foldl

Now write the moves function using foldl.

moves :: [Direction] -> Pos -> Pos
moves ds p = foldl ...

Exercise: moves foldl

Now write the moves function using foldl.

moves :: [Direction] -> Pos -> Pos
moves ds p = foldl (\p d -> move d p) p ds

That lambda is annoying, can we get rid of it?

Well move takes its arguments in the wrong order for foldl. Could we write a function to flip the arguments of a given binary function?

Exercise: flip’

Write a function flip’ that takes a binary function and returns a new function that takes its arguments in the opposite order.

flip' :: (a -> b -> c) -> b -> a -> c

Exercise: flip’

Write a function flip’ that takes a binary function and returns a new function that takes its arguments in the opposite order.

flip' :: (a -> b -> c) -> b -> a -> c
flip' f x y = f y x

Exercise: moves foldl

Now we can rewrite moves:

moves :: [Direction] -> Pos -> Pos
moves ds p = foldl (flip' move) p ds

Data Declarations: More Complex Branches

So far our datatype branches (each separated by |) have been simple, but they can be more complex:

data Shape = Circle Float | Rect Float Float

A Shape is either a Circle with a single Float argument or a Rect with two Float arguments.

Data Declarations: More Complex Branches

square :: Float -> Shape
square n = Rect n n

area :: Shape -> Float
area (Circle r) = pi * r^2
area (Rect x y) = x * y

Data Declarations: More Complex Branches

Each branch of the data declaration is really just a function which constructs a value of the type being declared:

> :t Circle
Circle :: Float -> Shape

> :t Rect
Rect :: Float -> Float -> Shape

Data Declarations: Maybe

The Maybe type is a useful to represent computations which might fail.

data Maybe a = Nothing | Just a

Notice that maybe takes a type argument.

We can then use this to define safe versions of previously unsafe functions such as division or head:

safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv m n = Just (m `div` n)

safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x

Rewriting Find

Remember the find function we wrote earlier?

type Assoc k v = [(k, v)]

find :: Eq k => k -> Assoc k v -> v
find k t = head [ v | (k', v) <- t, k == k' ]

Is this function safe?

No! It uses head which is unsafe! We could instead rewrite it using safeHead as follows:

find :: Eq k => k -> Assoc k v -> Maybe v
find k t = safeHead [ v | (k', v) <- t, k == k' ]

Case Expression

Something that we have briefly talked about but will become much more important is the case expression. It can be used to do pattern matching on a value the same way you do in function definitions.

case e of
    p1 -> e1
    p2 -> e2
    ...
    pn -> en

or more concretely:

foo :: Int -> String
foo x = case x of
    0 -> "zero"
    1 -> "one"
    _ -> "other"

Exercise: stringToInt

Write a function stringToInt that takes a string and returns the integer it represents. This function should return Maybe Int because if this process fails it should return Nothing. You can assume that you have the isDigit and digitToInt function available.

import Data.Char (isDigit, digitToInt)

stringToInt :: String -> Maybe Int

Exercise: stringToInt

Write a function stringToInt that takes a string and returns the integer it represents. This function should return Maybe Int because if this process fails it should return Nothing. You can assume that you have the isDigit and digitToInt function available.

import Data.Char (isDigit, digitToInt)

stringToInt :: String -> Maybe Int
stringToInt [] = Just 0
stringToInt (c:cs)
    | isDigit c = case stringToInt cs of
        Nothing -> Nothing
        Just n  -> Just (10 * n + digitToInt c)
    | otherwise = Nothing