Data
π Pattern β specific value or shape of input.
f :: type
f ... = case var of
pat1 -> result1
pat2 -> result2
...
patN -> resultN
f :: type
f pat1 = result1
f pat2 = result2
...
f patN = resultN
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!
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.
[]
x : xs
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 : []))
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
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
π©βπ¬ Always use pattern matching on lists instead of unsafe
head and tail functions whenever you need either of them.
isEmpty :: [Int] -> True
isEmpty _ = False
isEmpty [] = True
same :: Int -> Int -> True
same x x = True
same _ _ = False
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.
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 []
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)β
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],[])
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"
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 β 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
βββ data type name
β
β βββ Constructor name
β β
data User = MkUser String Int Bool
β β β
βββββββΌβββββ
β
β
Field types
data User = MkUser String Int Bool
deriving (Show) -- to display our type in GHCi
getUserName :: User -> String
getUserName (MkUser name _ _) = name
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
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 β choice of zero or more types
String
Int
Bool
"Hello!"
42
True
"Hello!"
42
True
Any possible option of a value from each type
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"]
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
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 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 β lightweight wrapper for an existing type. Can only have:
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
π° Costs
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)
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 β Haskell search engine to search Haskell package database by name, module or even type.
π 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 GoldenChest
reward :: Dragon -> RewardChest
reward ... = ...
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)
Simpler version (not in base)
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!"])
showInt :: Int -> String
showInt n = show n
showInt :: Int -> String
showInt = show
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 operator: the dot (.) operator
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
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]
π 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