Lecture 2
Data
Pattern Matching

Patterns

π Pattern β specific value or shape of input.
case-of
f :: type
f ... = case var of
pat1 -> result1
pat2 -> result2
...
patN -> resultNSyntax
f :: type
f pat1 = result1
f pat2 = result2
...
f patN = resultNExamples
not :: Bool -> Bool
not True = False
not False = TrueisZero :: Int -> Bool
isZero 0 = True
isZero n = Falseeval :: 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 _ = FalsesumOfTwoInThree :: [Int] -> Int
sumOfTwoInThree [x, _, y] = x + y
sumOfTwoInThree _ = 0oneOrTwoZeroes :: [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 _ = FalseDoes the following work?
β No! Because it checks only on lists of size two.

Structural List Patterns
[]
x : xsA 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
headWait! 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 : _) = xdropHead :: [Int] -> [Int]
dropHead [] = []
dropHead (_ : xs) = xssecondIsZero :: [Int] -> Bool
secondIsZero (_ : 0 : _) = True
secondIsZero _ = False
π 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 xsImproving 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 [] = Truesame :: 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: " ++ stringghci> 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


Product Type
Product type β zero or more types combined
String
Int
Bool
"Hello!"42True("Hello!", 42, True)
("Byeeee", 15, False)
("", 0, True)All possible combinations including values of all types


data: Product
βββ data type name
β
β βββ Constructor name
β β
data User = MkUser String Int Bool
β β β
βββββββΌβββββ
β
β
Field typesdata: Working with data
data User = MkUser String Int Bool
deriving (Show) -- to display our type in GHCiGetters
getUserName :: User -> String
getUserName (MkUser name _ _) = nameSetters
setUserName :: String -> User -> User
setUserName name (MkUser _ age isTired) = MkUser name age isTiredghci> :t MkUser
MkUser :: String -> Int -> Bool -> User
ghci> getUserName (MkUser "John" 29 True)
"John"
ghci> setUserName "Ivan" (MkUser "John" 29 True)
MkUser "Ivan" 29 TruegetUserAge :: User -> Int
getUserAge (MkUser _ age _) = agedata: 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 -> BoolGenerates 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!"42True"Hello!"
42
TrueAny possible option of a value from each type


Sum Types: Enumerations
data Color
= Red
| Green
| Bluedata Bool
= False
| TrueshowColor :: 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 Intdivide :: 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 resultA data type with 2 constructors
Each constructor has one field
data Answer
= NoResult
| Result Intdata Property
= Padding Int
| Clickable Bool Int
| Description StringRecursive data types
data IntList
= Empty
| Cons Int IntListlength :: IntList -> Int
length Empty = 0
length (Cons _ xs) = 1 + length xs1οΈβ£ 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)
126type
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 = StringStandard type aliases
β οΈ The type is the same! It's just a different name.
type Attack = Int
type Defense = Int
type Health = Intdamage :: Attack -> Defense -> Health -> Health
damage atk def hp = hp + def - atknewtype
newtype β lightweight wrapper for an existing type. Can only have:
- Exactly one constructor
- Exactly one field
type Attack = Int
type Defense = Int
type Health = Intnewtype Attack = MkAttack Int
newtype Defense = MkDefense Int
newtype Health = MkHealth Intdamage :: 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]
Hoogle

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])
ghci> :t MkChest
MkChest :: Int -> a -> Chest a
ghci> MkChest 100 True
MkChest { chestGold = 100
, chestTreasure = True}data RewardChest
= Bronze BronzeChest
| Silver SilverChest
| Golden GoldenChestreward :: Dragon -> RewardChest
reward ... = ...Common types
data Maybe a
= Nothing
| Just adata 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)
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 nshowInt :: Int -> String
showInt = show
Ξ·-reduction, part 2
onlyEven :: [Int] -> [Int]
onlyEven xs = filter even xsonlyEven :: [Int] -> [Int]
onlyEven = filter evenprod :: [Int] -> [Int] -> [Int]
prod xs ys = zipWith (*) xs ysprod :: [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)
ELI5 Composition

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)) listtakeEven5 = 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,844