Joseph Haugh
University of New Mexico
What is the type of the following expressions?
tail ["ciri", "geralt", "yennefer"] ::
What is the type of the following expressions?
tail ["ciri", "geralt", "yennefer"] :: [String]
tail :: [a] -> [a]
["ciri", "geralt", "yennefer"] :: [String]
What is the type of the following expressions?
map (filter even) [[1,2,3], [4,5,6], [7,8,9]] ::
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]]
What is the type of the following expressions?
foldr (++) ::
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]
What is the type of the following expressions?
map (foldr (++) []) ::
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]]
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].
The right hand side of the equal can be any valid type:
type Pos = (Int, Int)
type Trans = Pos -> Pos
A type declaration can take type level arguments:
type Pair a = (a, a)
type Assoc k v = [(k, v)]
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
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' ]
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?
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 Direction = North | East | South | West
A Direction is either North, East, South, or West.
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)
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
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)
Now write the moves function using foldl.
moves :: [Direction] -> Pos -> Pos
moves ds p = 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?
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
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
Now we can rewrite moves:
moves :: [Direction] -> Pos -> Pos
moves ds p = foldl (flip' move) p ds
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.
square :: Float -> Shape
square n = Rect n n
area :: Shape -> Float
area (Circle r) = pi * r^2
area (Rect x y) = x * y
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
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
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' ]
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"
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
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