Lecture 2
Data
Pattern Matching
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221107/pasted-from-clipboard.png)
Patterns
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221108/pasted-from-clipboard.png)
π Pattern β specific value or shape of input.
case-of
f :: type
f ... = case var of
pat1 -> result1
pat2 -> result2
...
patN -> resultN
Syntax
f :: type
f pat1 = result1
f pat2 = result2
...
f patN = resultN
Examples
not :: Bool -> Bool
not True = False
not False = True
isZero :: Int -> Bool
isZero 0 = True
isZero n = False
eval :: Char -> Int -> Int -> Int
eval op x y = case op of
'+' -> x + y
'-' -> x - y
'*' -> x * y
'/' -> div x y
_ -> 0
^ watch for indentation!
Patterns on lists
isEmpty :: [Int] -> Bool
isEmpty [] = True
isEmpty _ = False
sumOfTwoInThree :: [Int] -> Int
sumOfTwoInThree [x, _, y] = x + y
sumOfTwoInThree _ = 0
oneOrTwoZeroes :: [Int] -> Bool
oneOrTwoZeroes l = case l of
[0] -> True
[0, 0] -> True
_ -> False
-- check if a list has at least two elements
atLeastTwo :: [Int] -> Bool
atLeastTwo [_, _] = True
atLeastTwo _ = False
Does the following work?
β No! Because it checks only on lists of size two.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221119/pasted-from-clipboard.png)
Structural List Patterns
[]
x : xs
A list can be only one of those two:
1οΈβ£ Empty list
2οΈβ£ An element prepended to a list
βββ connecting head and tail
β `:` is both operator and pattern
β
x : xs
β β
β βββ tail
head
Wait! What about list literals [x, y, z]?
Syntax sugar π¬
[ 3 , 1 , 2 ]
3 : 1 : 2 : []
3 : (1 : (2 : []))
πͺ Heads or Tails?
headOrDef :: Int -> [Int] -> Int
headOrDef def [] = def
headOrDef _ (x : _) = x
dropHead :: [Int] -> [Int]
dropHead [] = []
dropHead (_ : xs) = xs
secondIsZero :: [Int] -> Bool
secondIsZero (_ : 0 : _) = True
secondIsZero _ = False
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221142/pasted-from-clipboard.png)
π List Recursion
sum :: [Int] -> Int
sum [] = 0
sum (x : xs) = x + sum xs
β οΈ Warning!
π Slow implementation!
count :: Int -> [Int] -> Int
count n list = go 0 list
where
go :: Int -> [Int] -> Int
go acc list =
if null list
then result
else if head list == n
then go (acc + 1) (tail list)
else go acc (tail list)
count :: Int -> [Int] -> Int
count n list = go 0 list
where
go :: Int -> [Int] -> Int
go acc [] = acc
go acc (x : xs)
| x == n = go (acc + 1) xs
| otherwise = go acc xs
Improving previous examples
π©βπ¬ Always use pattern matching on lists instead of unsafe
head and tail functions whenever you need either of them.
What's wrong, Haskell?
isEmpty :: [Int] -> True
isEmpty _ = False
isEmpty [] = True
same :: Int -> Int -> True
same x x = True
same _ _ = False
π» Code
π« Error
headOrDef :: Int -> [Int] -> Int
headOrDef def l = def
headOrDef _ (x : _) = x
π©βπ¬ Incorrect order of patterns
π©βπ¬ Variable is a "catch-all" pattern
π©βπ¬ Patterns on variable names are not supported
head :: [Int] -> Int
head (x : _) = x
π©βπ¬ Patterns don't cover all possible cases
isGreeting :: String -> Bool
isGreeting str = case str of
'H' : 'i' : '!' : _ -> True
_ -> False
π©βπ¬ No errors here,
just looks ugly.
Totality
example :: Bool -> [Int] -> Int
example True [] = 0
example False [x, y] = x + y
π A function is total if it is defined for all inputs of its corresponding type, or in other words, if a function returns the output on any possible values of the input types.
βοΈ GHC checks if a function is partial due to non-exhaustive patterns.
π Partial β non-total.
<interactive>:32:1: warning: [-Wincomplete-patterns]
Pattern match(es) are non-exhaustive
In an equation for βexampleβ:
Patterns not matched:
True (_:_)
False (_:_:_:_)
False [_]
False []
Data
Tuples
ghci> :t ('x', True)
('x', True) :: (Char, Bool)
ghci> :t ([True, False], "abc", 'F')
([True, False], "abc", 'F') :: ([Bool], [Char], Char)
ghci> fst ('x', True)
'x'
ghci> snd ('x', True)
True
ghci> fst (3, True, False)
<interactive>:39:5: error:
β’ Couldn't match expected type β(a, b0)β
with actual type β(a0, Bool, Bool)β
Working with tuples
splitAtPos3 :: [Int] -> ([Int], [Int])
splitAtPos3 l = (take 3 l, drop 3 l)
ghci> splitAtPos3 [1..10]
([1,2,3],[4,5,6,7,8,9,10])
ghci> splitAtPos3 [3, 1]
([3,1],[])
Returning
Matching
showTriple :: (Bool, Int, String) -> String
showTriple (b, n, string) =
if b
then "The number is: " ++ show n
else "The string is: " ++ string
ghci> showTriple (True, 42, "hello")
"The number is: 42"
ghci> showTriple (False, 42, "hello")
"The string is: hello"
Algebraic Data Type (ADT)
ADT β a type formed by combing other types using either
product or sum types.
Product type
Sum type
Combining types using both
Choice of types, either one or another
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263461/cross.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263467/plus.png)
Product Type
Product type β zero or more types combined
String
Int
Bool
"Hello!"
42
True
("Hello!", 42, True)
("Byeeee", 15, False)
("", 0, True)
All possible combinations including values of all types
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263461/cross.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263461/cross.png)
data: Product
βββ data type name
β
β βββ Constructor name
β β
data User = MkUser String Int Bool
β β β
βββββββΌβββββ
β
β
Field types
data: Working with data
data User = MkUser String Int Bool
deriving (Show) -- to display our type in GHCi
Getters
getUserName :: User -> String
getUserName (MkUser name _ _) = name
Setters
setUserName :: String -> User -> User
setUserName name (MkUser _ age isTired) = MkUser name age isTired
ghci> :t MkUser
MkUser :: String -> Int -> Bool -> User
ghci> getUserName (MkUser "John" 29 True)
"John"
ghci> setUserName "Ivan" (MkUser "John" 29 True)
MkUser "Ivan" 29 True
getUserAge :: User -> Int
getUserAge (MkUser _ age _) = age
data: Records
ghci> john = MkUser {userAge = 29, userIsTired = True, userName = "John"}
ghci> john
MkUser {userName = "John", userAge = 29, userIsTired = True}
ghci> userName john
"John"
ghci> ivan = john { userName = "Ivan", userIsTired = False }
ghci> ivan
MkUser {userName = "Ivan", userAge = 29, userIsTired = False}
data User = MkUser
{ userName :: String
, userAge :: Int
, userIsTired :: Bool
}
userName :: User -> String
userAge :: User -> Int
userIsTired :: User -> Bool
Generates the following top-level functions
β Initialization using names
β Record-update syntax
Sum Type
Sum type β choice of zero or more types
String
Int
Bool
"Hello!"
42
True
"Hello!"
42
True
Any possible option of a value from each type
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263467/plus.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263467/plus.png)
Sum Types: Enumerations
data Color
= Red
| Green
| Blue
data Bool
= False
| True
showColor :: Color -> String
showColor color = case color of
Red -> "red"
Green -> "green"
Blue -> "blue"
ghci> showColor Blue
"blue"
ghci> map showColor [Red, Green, Blue, Green]
["red","green","blue","green"]
Sum Types
data Result
= Error String
| Ok Int
divide :: Int -> Int -> Result
divide _ 0 = Error "Division by zero!"
divide x y = Ok (div x y)
ghci> showResult (divide 15 0)
"Error: Division by zero!"
ghci> showResult (divide 15 3)
"Ok: 5"
showResult :: Result -> String
showResult (Error msg) = "Error: " ++ msg
showResult (Ok result) = "Ok: " ++ show result
A data type with 2 constructors
Each constructor has one field
data Answer
= NoResult
| Result Int
data Property
= Padding Int
| Clickable Bool Int
| Description String
Recursive data types
data IntList
= Empty
| Cons Int IntList
length :: IntList -> Int
length Empty = 0
length (Cons _ xs) = 1 + length xs
1οΈβ£ Empty list
2οΈβ£ An element prepended to a list
nzeroes :: Int -> IntList
nzeroes 0 = Empty
nzeroes n = Cons 0 (nzeroes (n - 1))
ghci> nzeroes 3
Cons 0 (Cons 0 (Cons 0 Empty))
ghci> length (nzeroes 126)
126
type
type MyTriples = [(Int, Bool, String)]
type IntPredicate = Int -> Bool
βΉοΈ Use the type keyword to giveΒ another name to an existing type
type String = [Char]
type FilePath = String
Standard type aliases
β οΈ The type is the same! It's just a different name.
type Attack = Int
type Defense = Int
type Health = Int
damage :: Attack -> Defense -> Health -> Health
damage atk def hp = hp + def - atk
newtype
newtype β lightweight wrapper for an existing type. Can only have:
- Exactly one constructor
- Exactly one field
type Attack = Int
type Defense = Int
type Health = Int
newtype Attack = MkAttack Int
newtype Defense = MkDefense Int
newtype Health = MkHealth Int
damage :: Attack -> Defense -> Health -> Health
damage (MkAttack atk) (MkDefense def) (MkHealth hp) =
MkHealth (hp + def - atk)
π° Benefits
- No runtime cost
- Literally new type
π° Costs
- Wrapping and unwrapping
Polymorphism
Parametric polymorphism
dup :: a -> (a, a)
dup x = (x, x)
β¬οΈ Specific types start with an upper letter: String, Int, Bool, ...
β¬οΈ Type variables start with a lower letter: a, b, c, f, m, ...
ghci> dup 'x'
('x','x')
ghci> :t dup 'x'
dup 'x' :: (Char, Char)
ghci> dup True
(True,True)
ghci> :t dup True
dup True :: (Bool, Bool)
Real types
fst :: (a, b) -> a
snd :: (a, b) -> b
(++) :: [a] -> [a] -> [a]
(:) :: a -> [a] -> [a]
head :: [a] -> a
tail :: [a] -> [a]
reverse :: [a] -> [a]
take :: Int -> [a] -> [a]
drop :: Int -> [a] -> [a]
zip :: [a] -> [b] -> [(a, b)]
map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221414/pasted-from-clipboard.png)
Hoogle
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221420/pasted-from-clipboard.png)
Hoogle β Haskell search engine to search Haskell package database by name, module or even type.
Polymorphic types
π A treasure chest always contains gold and some reward
data Chest a = MkChest
{ chestGold :: Int
, chestTreasure :: a
}
type BronzeChest = Chest Armor
type SilverChest = Chest (Sword, Armor)
type GoldenChest = Chest (Artifact, Sword, [Gemstone])
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221423/pasted-from-clipboard.png)
ghci> :t MkChest
MkChest :: Int -> a -> Chest a
ghci> MkChest 100 True
MkChest { chestGold = 100
, chestTreasure = True}
data RewardChest
= Bronze BronzeChest
| Silver SilverChest
| Golden GoldenChest
reward :: Dragon -> RewardChest
reward ... = ...
Common types
data Maybe a
= Nothing
| Just a
data Either a b
= Left a
| Right b
βΉοΈ Optional value
βΉοΈ Choice between two types
βΉοΈ List
data [] a
= []
| a : [a]
data List a
= Empty
| Cons a (List a)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221424/pasted-from-clipboard.png)
Simpler version (not in base)
Common functions
find :: (a -> Bool) -> [a] -> Maybe a
ghci> find (> 4) [3, 1, 2]
Nothing
ghci> find (> 4) [3, 5, 1, 2, 10]
Just 5
βΉοΈ Find an optional value
fromMaybeInt :: Maybe Int -> Int
fromMaybeInt Nothing = 0
fromMaybeInt (Just n) = n
βΉοΈ Optional number to zero default
βΉοΈ Lists of different types
partitionEithers :: [Either a b] -> ([a], [b])
ghci> partitionEithers [Left 3, Right "hi!", Left 5]
([3,5],["hi!"])
Back to Functions
Eta-reduction, part 1
showInt :: Int -> String
showInt n = show n
showInt :: Int -> String
showInt = show
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263927/pasted-from-clipboard.png)
Ξ·-reduction, part 2
onlyEven :: [Int] -> [Int]
onlyEven xs = filter even xs
onlyEven :: [Int] -> [Int]
onlyEven = filter even
prod :: [Int] -> [Int] -> [Int]
prod xs ys = zipWith (*) xs ys
prod :: [Int] -> [Int] -> [Int]
prod = zipWith (*)
ghci> :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
ghci> zipWith (*) [3, 1, 2] [10, 20, 30]
[30,20,60]
π©βπ¬ You don't always want to eta-reduce because variable names can be helpful in some situations
Function Composition
βοΈ Function composition operator: the dot (.) operator
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9263723/pasted-from-clipboard.png)
ELI5 Composition
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2050009/images/9221457/composition-diagram.png)
Function Composition
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
Examples
ghci> (length . show) True
4
ghci> map (length . words) ["Hi all", "bi dup boo", "x"]
[2,3,1]
ghci> map (even . length . words) ["Hi all", "bi dup boo", "x"]
[True,False,False]
Composition + Eta-reduction
π A function that returns first 5 lists of even length
takeEven5 :: [[a]] -> [[a]]
takeEven5 list = take 5 (filter (\l -> even (length l)) list)
1οΈβ£ Rewrite lambda to composition
takeEven5 list = take 5 (filter (\l -> (even . length) l) list)
2οΈβ£ Eta-reduce lambda
takeEven5 list = take 5 (filter (even . length) list)
3οΈβ£ Rewrite main function to composition
takeEven5 list = (take 5 . filter (even . length)) list
takeEven5 = take 5 . filter (even . length)
4οΈβ£ Eta-reduce main function
More sources
Lecture 2: Data types
By Haskell Beginners 2022
Lecture 2: Data types
Custom types in Haskell
- 4,221