Haskell - ttulka/programming GitHub Wiki
Haskell is a purely functional statically typed lazy programming language.
- functions have no side effects
- functions are referential transparent (same result for calls with the same parameters)
- Haskell has type inference
- you can’t set a variable to one value and then set it to something else later on
- Haskell won’t execute functions until it needs to show you a result
- Haskell doesn’t have a concept of pointers, just values
:l <source-file-without-.hs-suffix> -- load a source code
:r -- reload the file
:t <expression> -- type of the expression
:k <type> -- kind of the type
:m + <module>,<module>,... -- import modules
ghc --make <source-file-without-.hs-suffix> -- compile a program
doubleMe x = x + x
doubleMe 4 -- 8
doubleUs x y = x * 2 + y * 2
doubleUs 4 9 -- 26
doubleUs 2.3 34.2 -- 73.0
Every function in Haskell officially takes only one parameter. All the functions that accepted multiple parameters are curried functions.
-- :t max
max :: Ord a => a -> a -> a -- same as `a -> (a -> a)`
-- These two calls are equivalent:
max 4 5
(max 4) 5
max4 = max 4
max4 5
5
divideByTen :: (Floating a) => a -> a
divideByTen = (/10)
divideByTen 200 -- 20.0
(/10) 200 -- 20.0
isUpperAlphanum :: Char -> Bool
isUpperAlphanum = (`elem` ['A'..'Z'])
isUpperAlphanum 'A' -- True
isUpperAlphanum 'a' -- False
Generally, a function like foo a = bar b a
, can be rewrited as foo = bar b
because of currying:
sum' :: (Num a) => [a] -> a
sum' xs = foldl (+) 0 xs
sum' :: (Num a) => [a] -> a
sum' = foldl (+) 0
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
applyTwice (+2) 1 -- 5
Lambdas are anonymous functions that we use when we need a function only once.
\<parameters> -> <body>
map (\x -> x + 3) [1,2,3] -- [4,5,6]
-- is same as `map (+3) [1,6,3,2]`
map (\(a,b) -> a + b) [(1,2),(3,5)] -- [3,8]
-- These two functions are equivalent:
addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z.
addThree :: Int -> Int -> Int -> Int
addThree' = \x -> \y -> \z -> x + y + z
- When ommiting parentheses, everything to the right of the arrow
->
belongs to the lambda.
flip' :: (a -> b -> c) -> b -> a -> c
flip' f = \x y -> f y x
($) :: (a -> b) -> a -> b
f $ x = f x
Function application with a space is left-associative (so f a b c
is the same as ((f a) b) c)
, while function application with $
is right-associative.
-
$
function has the lowest precedence:
sum (map sqrt [1..130])
sqrt (3 + 4 + 9)
-- could be rewitten as:
sum $ map sqrt [1..130]
sqrt $ 3 + 4 + 9
-
$
function is right-associative:f $ g $ x
is equivalent tof $ (g $ x)
:
sum (filter (> 10) (map (*2) [2..10]))
-- could be rewitten as:
sum $ filter (> 10) $ map (*2) [2..10]
-
$
lets us treat function application like just another function:
map ($ 3) [(4+), (10*), (^2), sqrt] -- [7.0,30.0,9.0,1.7320508075688772]
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
(+1) . (*100) $ 4 -- 401
map (\x -> negate (abs x)) [1,2,-3] -- [-1,-2,3]
map (\xs -> negate (sum (tail xs))) [[1..5],[3..6]] -- [-14,-15]
-- is equivalent to:
map (negate . abs) [1,2,-3]
map (negate . sum . tail) [[1..5],[3..6]]
-- these lines are equivalent:
sum (replicate 3 (max 1 2)) -- can be understood as `(sum . replicate 3) max 1 2`
sum . replicate 3 $ max 1 2
Because of currying we can ommit the parameter in the function definition:
fn x = ceiling (negate (tan (cos (max 50 x)))) -- definition with the parameter
fn = ceiling . negate . tan . cos . max 50 -- same function in point-free style
plusThreeTimesTwo :: Num c => c -> c
plusThreeTimesTwo = (*) 2 . (+) 3
plusThreeTimesTwo 2 -- 10
- the
else
clause is mandatory
if x > 100 then x else x * 2
Lists in Haskell are homogeneous data structure.
- strings are just lists of chars
-
[1,2,3]
is just syntactic sugar for1:2:3:[]
[] -- empty list
0:[1,2] -- [0,1,2]
[1,2] ++ [3,4,5] -- [1,2,3,4,5]
'A':" SMALL CAT" -- "A SMALL CAT"
"Steve Buscemi" !! 6 -- B
[1,2,3] !! 0 -- 1
[1,2,3] !! 1 -- 2
[1,2,3] !! 3 -- *** Exception: index too large
b = [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
b ++ [[1,1,1,1]] -- [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3],[1,1,1,1]]
[6,6,6]:b -- [[6,6,6],[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
b !! 2 -- [1,2,2,3,4]
[3,2,1] > [2,10,100] -- True
[3,4,2] > [2,4] -- True
[3,4,2] == [3,4,2] -- True
-- [head][--------tail----------]
-- [----------init--------][last]
head [5,4,3,2,1] -- 5
tail [5,4,3,2,1] -- [4,3,2,1]
last [5,4,3,2,1] -- 1
init [5,4,3,2,1] -- [5,4,3,2]
length [1,2,3] -- 3
null [1,2,3] -- False
null [] -- True
reverse [1,2,3] -- [3,2,1]
take 2 [1,2,3] -- [1,2]
take 0 [1,2,3] -- []
take 9 [1,2,3] -- [1,2,3]
drop 2 [1,2,3] -- [3]
drop 0 [1,2,3] -- [1,2,3]
drop 9 [1,2,3] -- []
maximum [1,2,3] -- 3
minimum [1,2,3] -- 1
sum [1,2,3,4] -- 10
product [1,2,3,4] -- 24
elem 2 [1,2,3] -- True
elem 2 [3,4,5] -- False
[1..10] -- [1,2,3,4,5,6,7,8,9,10]
['a'..'z'] -- "abcdefghijklmnopqrstuvwxyz"
[2, 4..10] -- [2,4,6,8,10]
take 5 [13, 26..] -- [13,26,39,52,65]
take 10 (cycle [1,2,3]) -- [1,2,3,1,2,3,1,2,3,1]
take 12 (cycle "LOL ") -- "LOL LOL LOL "
take 10 (repeat 5) -- [5,5,5,5,5,5,5,5,5,5]
replicate 3 10 -- [10,10,10]
[0.1, 0.3 .. 1] -- [0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
[x*2 | x <- [1..10]] -- [2,4,6,8,10,12,14,16,18,20]
[x*2 | x <- [1..10], x*2 >= 12] -- [12,14,16,18,20]
boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]
boomBangs [7..13] -- ["BOOM!","BOOM!","BANG!","BANG!"]
[ x | x <- [10..20], x /= 13, x /= 15, x /= 19] -- [10,11,12,14,16,17,18,20]
[x+y | x <- [10,100], y <- [1,2,3]] -- [11,12,13,101,102,103]
length' xs = sum [1 | _ <- xs] -- underscore `_` is a temporary variable we don't care about
map (+3) [1,2,3] -- [4,5,6]
map (++ "!") ["Bang", "Pow"] -- ["Bang!","Pow!"]
map (replicate 3) [3..5] -- [[3,3,3],[4,4,4],[5,5,5]]
map (map (^2)) [[1,2],[3,4,5]] -- [[1,4],[9,16,25]]
listOfFncs = map (*) [0..] -- we will get back a list of functions
(listOfFncs !! 4) 5
-- Getting the element with the index 4 returns a function equivalent to (4*).
-- Then we just apply 5 to that function, which is the same as (4*) 5.
filter (>3) [5,3,2,4] -- [5,4]
filter (==3) [1,2,3,4,5] -- [3]
filter (`elem` ['a'..'z']) "u LaUgH aT mE" -- "uagam"
filter (<15) (filter even [1..10]) -- [2,4,6,8,10]
quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
let smallerOrEqual = filter (<= x) xs
larger = filter (> x) xs
in quicksort smallerOrEqual ++ [x] ++ quicksort larger
- Folds allow you to reduce a data structure (like a list) to a single value.
- A fold takes a binary function (one that takes two parameters, such as + or div), a starting value (often called the accumulator ), and a list to fold up.
- A right fold over the list [1,2,3], is essentially doing this:
f 1 (f 2 (f 3 acc))
sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs
-- alternatively `sum' = foldl (+) 0`
sum' [1,2,3] -- 6
map' :: (a -> b) -> [a] -> [b]
map' f xs = foldr (\x acc -> f x : acc) [] xs
-- is much cheaper than the fold-left alternative:
map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs
elem' :: (Eq a) => a -> [a] -> Bool
elem' y ys = foldr (\x acc -> if x == y then True else acc) False ys
reverse' :: [a] -> [a]
reverse' = foldl (\acc x -> x : acc) []
product' :: (Num a) => [a] -> a
product' = foldl (*) 1
filter' :: (a -> Bool) -> [a] -> [a]
filter' p = foldr (\x acc -> if p x then x : acc else acc) []
last' :: [a] -> a
last' = foldl1 (\_ x -> x)
- work much like
foldl
andfoldr
, except assume the first (resp. last) element to be the starting accumulator.
maximum' :: (Ord a) => [a] -> a
maximum' = foldl1 max
The scanl
and scanr
functions are like folds, except they report all the intermediate accumulator states in the form of a list.
scanl (+) 0 [1,2,3] -- [0,1,3,6]
scanr (+) 0 [1,2,3] -- [6,5,3,0]
scanl (flip (:)) [] [1,2,3] -- [[],[1],[2,1],[3,2,1]]
Tuples are used to store several heterogeneous elements as a single value.
(1, 3) -- (1,3)
(3, 'a', "hello") -- (3,'a',"hello")
(1, 50.4, "hello", 'b') -- (1,50.4,"hello",'b')
[(1,2),(8,11,5),(4,5)] -- error: Couldn't match expected type `(a, b)`
fst (8, 11) -- 8
snd (8, 11) -- 11
zip [1,2,3] [5,5,5] -- [(1,5),(2,5),(3,5)]
zip [1,2,3,4,5] ["a","b"] -- [(1,"a"),(2,"b")]
zip ["a","b","c"] [1..] -- [("a",1),("b",2),("c",3)]
phoneBook = [("betty","555-2938"), ("bonnie","452-2928"), ("wendy","939-8282")]
--
findKey :: (Eq k) => k -> [(k, v)] -> v
findKey key xs = snd . head . filter (\(k, v) -> key == k) $ xs
findKey "wendy" phoneBook -- "939-8282"
findKey "diana" phoneBook -- "*** Exception: head: empty list
--
findKey :: (Eq k) => k -> [(k, v)] -> Maybe v
findKey key xs = foldr (\(k, v) acc -> if key == k then Just v else acc) Nothing xs
findKey "wendy" phoneBook -- Just "939-8282"
findKey "diana" phoneBook -- Nothing
### Pattern Matching
Pattern matching is used to specify patterns to which some data should conform and to deconstruct the data according to those patterns.
```haskell
lucky :: Int -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"
lucky 1 -- "Sorry, you're out of luck, pal!"
lucky 7 -- "LUCKY NUMBER SEVEN!"
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial 8 -- 40320
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
addVectors (1,2) (10,20) -- (11,22)
first (x, _, _) = x
first (1,2,3) -- 1
As-patterns allow you to break up an item according to a pattern, while still keeping a reference to the entire original item.
firstLetter "" = "Empty string, whoops!"
firstLetter myParam@(x:xs) = "The first letter of " ++ myParam ++ " is " ++ [x]
firstLetter "Dracula" -- "The first letter of Dracula is D"
initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstname
(l:_) = lastname
cylinder :: Double -> Double -> Double
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^ 2
in sideArea + 2 * topArea
- can be used anywhere in the code:
4 * (let a = 9 in a + 1) + 2 -- 42
[let square x = x * x in (square 5, square 3, square 2)] -- [(25,9,4)]
- can be separated with semicolons:
(let a = 2; b = 5 in a*b, let foo="Hey "; bar = "there!" in foo ++ bar) -- (10,"Hey there!")
max' a b
| a <= b = b
| otherwise = a
max' 2 5 -- 5
weightTell :: Int -> String
weightTell weight
| weight <= 50 = "You're underweight, you emo, you!"
| weight <= 75 = "You're supposedly normal. Pffft, I bet you're ugly!"
| weight <= 95 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
weightTell 60 -- "You're supposedly normal. Pffft, I bet you're ugly!"
bmiTell :: Double -> Double -> String
bmiTell weight height
| bmi <= 18.5 = "You're underweight, you emo, you!"
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
case expression of pattern -> result
pattern -> result
...
head' :: [a] -> a
head' xs = case xs of [] -> error "No head for empty lists!"
(x:_) -> x
Pattern matching on function parameters can be done only when defining functions, but case expressions can be used anywhere:
describeList :: [a] -> String
describeList ls = "The list is " ++ case ls of [] -> "empty."
[x] -> "a singleton list."
xs -> "a longer list."
Because pattern matching in function definitions is the same as using case expressions, we could have also defined the describeList
function like this:
describeList ls = "The list is " ++ what ls
where what [] = "empty."
what [x] = "a singleton list."
what xs = "a longer list."
maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximum of empty list!"
maximum' [x] = x
maximum' (x:xs) = max x (maximum' xs)
reverse' :: [a] -> [a]
reverse' [] = []
reverse' (x:xs) = reverse' xs ++ [x]
replicate' :: Int -> a -> [a]
replicate' n x
| n <= 0 = []
| otherwise = x : replicate' (n-1) x
take' :: (Num i, Ord i) => i -> [a] -> [a]
take' n _
| n <= 0 = []
take' _ [] = []
take' n (x:xs) = x : take' (n-1) xs
quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
let smallerOrEqual = [a | a <- xs, a <= x]
larger = [a | a <- xs, a > x]
in quicksort smallerOrEqual ++ [x] ++ quicksort larger
quicksort "the quick brown fox jumps over the lazy dog"
" abcdeeefghhijklmnoooopqrrsttuuvwxyz"
data Bool = False | True
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
area :: Shape -> Float
area (Circle _ r) = pi * r ^ 2
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
area $ Circle (Point 0 0) 10 -- 314.15927
area $ Rectangle (Point 0 0) (Point 100 100) -- 10000.0
baseRect :: Float -> Float -> Shape
baseRect width height = Rectangle (Point 0 0) (Point width height)
data Person = Person {
firstName :: String,
lastName :: String,
age :: Int} deriving (Show)
guy = Person "Charles" "Bukowski" 74 -- alternatively:
guy = Person{ age=74, lastName="Bukowski", firstName="Charles" } -- any order
firstName guy -- "Charles" // the function is generated automatically
describePerson :: Person -> String
describePerson (Person {firstName = first, lastName = last, age = age}) =
"Person " ++ first ++ " " ++ last ++ " is " ++ show age
describePerson guy -- "Person Charles Bukowski is 74"
data Maybe a = Just a | Nothing -- the `a` here is the type parameter
Depending on what we want this data type to hold when it's not Nothing
, this type constructor can end up producing a type of Maybe Int
, Maybe Person
, and so on. No value can have a type of just Maybe
, because that’s not a type—it's a type constructor.
Just "abc" -- Just "abc"
:t Just "abc" -- Just "abc" :: Maybe [Char]
Just 84 -- Just 84
:t Just 84 -- Just 84 :: (Num a) => Maybe a
Just 1 :: Maybe Double -- Just 1.0
:t Nothing -- Nothing :: Maybe a
We say that a type is concrete if it doesn’t take any type parameters at all (like Int
or Bool
), or if it takes type parameters and they’re all filled up (like Maybe Char
). If you have some value, its type is always a concrete type.
data IntMaybe = INothing | IJust Int
Like functions, type constructors can be partially applied.
Classes in Haskell are defining a behavior. First we create our type and then we make it an instance of a class depending what behavior we need.
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Bounded, Enum)
Wednesday -- Wednesday
show Wednesday -- "Wednesday"
read "Wednesday" :: Day -- Wednesday
Saturday == Sunday -- False
Saturday > Friday -- True
Monday `compare` Wednesday -- LT
minBound :: Day -- Monday
maxBound :: Day -- Sunday
succ Monday -- Tuesday
pred Saturday -- Friday
[Thursday .. Saturday] -- [Thursday,Friday,Saturday]
type String = [Char]
type PhoneNumber = String
type Name = String
type PhoneBook = [(Name, PhoneNumber)]
type AssocList k v = [(k, v)]
data Either a b = Left a | Right b
deriving (Eq, Ord, Read, Show)
Right 20 -- Right 20
:t Left 'a' -- Left 'a' :: Either Char b
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)
singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree
treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
| x == a = Node x left right
| x < a = Node a (treeInsert x left) right
| x > a = Node a left (treeInsert x right)
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
| x == a = True
| x < a = treeElem x left
| x > a = treeElem x right
numsTree = foldr treeInsert EmptyTree [1,5,4,2]
numsTree
Node 2
(Node 1 EmptyTree EmptyTree)
(Node 4 EmptyTree
(Node 5 EmptyTree EmptyTree)
)
treeElem 1 numsTree -- True
treeElem 8 numsTree -- False
-- define a new type class:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
-- create an instance of that class:
data TrafficLight = Red | Yellow | Green
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = False
Red == Red -- True
Red == Yellow -- False
Red -- error: No instance for (Show TrafficLight)
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"
Red -- Red light
Because ==
was defined in terms of /=
and vice versa in the class declaration, we needed to overwrite only one of them in the instance declaration.
class (Eq a) => Num a where
...
This is just like writing class Num a where
, but we state that our type a must be an instance of Eq
.
That’s all there is to subclassing — it’s just a class constraint on a class declaration!
For example Maybe
is not a concrete type, it's a type constructor that takes one parameter and then produces a concrete type (so you can't have a function of the type a -> Maybe
, but it's okay to have a -> Maybe a
or Maybe Int -> Maybe String
).
-- instance Eq Maybe where -- doesn't compile
-- make all types of the form `Maybe something` an instance of `Eq`:
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
We use ==
on the contents of the Maybe
, so we have the assurance that what the Maybe
contains can be used with Eq
- that's why we needed to add the class constraint.
-- (==) :: Maybe -> Maybe -> Bool -- doesn’t make much sense
(==) :: (Eq m) => Maybe m -> Maybe m -> Bool -- okay
A kind is more or less the type of a type.
-
*
indicates that the type is a concrete type - concrete type is a type that doesn’t take any type parameters
- values can have only types that are concrete types
:k Int -- Int :: *
-- `Maybe` type constructor takes one concrete type (like Int) and returns a concrete type (like Maybe Int):
:k Maybe -- Maybe :: * -> *
:k Maybe Int -- Maybe Int :: *
-- Either takes two concrete types as type parameters to produce a concrete type:
:k Either -- Either :: * -> * -> *
:k Either Int -- Either Int :: * -> *
:k Either Int String -- Either Int String :: *
The Functor
type class is for things that can be mapped over (e.g. list).
class Functor f where
fmap :: (a -> b) -> f a -> f b
fmap
takes a function from one type to another and a functor value applied with one type and returns a functor value applied with another type.
-- `map` is a `fmap` that works only on lists:
map :: (a -> b) -> [a] -> [b]
-- the list's instance of the Functor type class:
instance Functor [] where
fmap = map
Here’s how Maybe
is a functor:
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
-- If it’s an empty value of Nothing, then just return a Nothing.
-- If it’s a single value packed in a `Just`, then we apply the function on the contents of the `Just`
fmap (*2) Nothing -- Nothing
fmap (*2) (Just 200) -- Just 400
We wrote instance Functor Maybe where
instead of instance Functor (Maybe m) where
because a functor wants a type constructor that takes one type, and not a concrete type.
The Functor
type class wants a type constructor that takes only one type parameter, but Either
takes two - we’ll partially apply Either by feeding it only one parameter, so that it has one free parameter:
data Either a b = Left a | Right b
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
We can think of the Left
part as sort of an empty box with an error message written on the side telling us why it’s empty.
- if we map the id function over a functor value, the functor value that we get back should be the same as the original
functor value.
fmap id = id
- composing two functions and then mapping the resulting function over a functor should be the same as first mapping one function over the functor and then mapping the other one.
fmap (f . g) = fmap f . fmap g
Applicative
type class, in the Control.Applicative
module defines two functions: pure
and <*>
.
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Applicative
instance implementation for Maybe
:
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
Just (+3) <*> Just 9 -- Just 12
pure (+3) <*> Just 9 -- Just 12
Just (++"hahah") <*> Nothing -- Nothing
pure (+) <*> Just 3 <*> Just 5 -- Just 8
pure (+) <*> Nothing <*> Just 5 -- Nothing
pure (+) <*> Just 3 <*> Nothing -- Nothing
Control.Applicative
exports a function called <$>
, which is just fmap as an infix operator:
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
f <$> x = fmap f x
(++) <$> Just "hey" <*> Just "ho" -- Just "heyho"
pure f <*> x = fmap f x
pure id <*> v = v
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
pure f <*> pure x = pure (f x)
u <*> pure y = pure ($ y) <*> u
Monads are just beefed-up applicative functors, much like applicative functors are beefed-up functors.
Monads are a natural extension of applicative functors, and they provide a solution to the following problem: If we have a value with a context, m a
, how do we apply to it a function that takes a normal a
and returns a value with a context? In other words, how do we apply a function of type a -> m b
to a value of type m a
? Essentially, we want this function:
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b -- `>>=` function is called *bind*.
class Monad m where
return :: a -> m a -- pure function
(>>=) :: m a -> (a -> m b) -> m b -- bind function
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
Maybe
is an instance of Monad
:
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing
return "WHAT" :: Maybe String -- Just "WHAT"
Just 9 >>= \x -> return (x*10) -- Just 90
Nothing >>= \x -> return (x*10) -- Nothing
For gluing together monadic values in sequence.
foo :: Maybe String
foo = Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y)))
-- is the same as:
foo :: Maybe String
foo = do
x <- Just 3
y <- Just "!"
Just (show x ++ y)
foo -- Just "3!"
do
expressions are just different syntax for chaining monadic values.
- In a
do
expression, every line that isn’t alet
line is a monadic value - To inspect its result, we use
<-
(except the last monadic value)
In do
notation, when we bind monadic values to names, we can utilize pattern matching, just as in let
expressions and function parameters:
justH :: Maybe Char
justH = do
(x:xs) <- Just "hello"
return x
When pattern matching fails in a do
expression, the fail function (part of the Monad
type class) enables it to result in a failure in the context of the current monad, instead of making the program crash.
-- the default implementation does make the program crash
fail :: (Monad m) => String -> m a
fail msg = error msg
```haskell
-- implementation for `Maybe` ignores the error message and makes a `Nothing`:
fail _ = Nothing
If we take a value, put it in a default context with return
, and then feed it to a function by using >>=
, that’s the same as just taking the value and applying the function to it.
-
return x >>= f
is same asf x
.
return 3 >>= (\x -> Just (x+100000)) -- Just 100003
(\x -> Just (x+100000)) 3 -- Just 100003
If we have a monadic value and we use >>=
to feed it to return
, the result is our original monadic value.
-
m >>= return
is same as justm
Just "abc" >>= (\x -> return x) -- Just "abc"
When we have a chain of monadic function applications with >>=
, it shouldn’t matter how they’re nested.
- doing
(m >>= f) >>= g
is just like doingm >>= (\x -> f x >>= g)
When we look at the law as a law of compositions, it states thatf <=< (g <=< h)
should be the same as(f <=< g) <=< h
.
liftM :: (Monad m) => (a -> b) -> m a -> m b -- pretty like `fmap`
liftM f m = m >>= (\x -> return (f x))
liftM (*3) (Just 8) -- Just 24
join :: (Monad m) => m (m a) -> m a -- flatts monadic values
join mm = do
m <- mm
m
join (Just (Just 9)) -- Just 9
join (Just Nothing) -- Nothing
join Nothing -- Nothing
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
binSmalls :: Int -> Int -> Maybe Int
binSmalls acc x
| x > 9 = Nothing
| otherwise = Just (acc + x)
foldM binSmalls 0 [2,8,3,1] -- Just 14
foldM binSmalls 0 [2,11,3,1] -- Nothing
- Functors: apply vanilla functions to structures.
- Applicatives: also, some functions are in the structure.
- Monads: also, some functions produce the structure.
A module is essentially a file that defines some functions, types, and type classes. A program is a collection of modules.
import Data.List
Data.List (nub, sort) -- only functions `nub` and `sort`
import Data.List hiding (nub) -- all the functions except `nub`
import qualified Data.Map -- call the function by `Data.Map.nub`
import qualified Data.Map as M -- call the function by `M.nub`
module MyDir.MyModule (fce1, fce2, ...) where
fce1 :: Num a => a -> a
fce1 = (+) 2
...
digitToInt '2' -- 2
digitToInt 'A' -- 10
ord 'a' -- 97
chr 97 -- 'a'
words "hello world!" -- ["hello","world!"]
sort [2,3,1] -- [1,2,3]
tails "abc" -- ["abc","bc","c",""]
tails [1,2,3] -- [[1,2,3],[2,3],[3],[]]
isPrefixOf "ab" "abc" -- True
any (> 4) [1,2,3] -- False
find (== 'a') "abc" -- Just 'a'
find (> 4) [1,2,3] -- Nothing
foldl' (+) 0 (replicate 1000000 1) -- 1000000 (no stack overflow errors)
fromList [("MS",1),("MS",2),("MS",3)] -- fromList [("MS",3)]
Map.lookup "betty" phoneBook -- Just "555-2938"
Map.lookup "diana" phoneBook -- Nothing
newBook = Map.insert "diana" "841-9021" phoneBook
Map.lookup "diana" phoneBook -- Just "841-9021"
Map.size phoneBook -- 3
Map.size newBook -- 4
:t putStrLn -- putStrLn :: String -> IO ()
By putting I/O actions together with do
syntax, we glued them into one I/O action:
helloworld.hs
main = do
putStrLn "Hello, what's your name?"
name <- getLine
putStrLn ("Hey " ++ name ++ ", you rock!")
- If we’re taking data out of an I/O action, we can take it out only when we’re inside another I/O action.
- To get the value out of an I/O action, you must perform it inside another I/O action by binding it to a name with
<-
In Haskell (and in I/O actions specifically), return
makes an I/O action out of a pure value.
- using
return
doesn’t cause the I/O do block to end in execution -
return
is sort of the opposite of<-
putStr "Hey, "
putStr "I'm "
putStrLn "Andy!"
putChar 't'
print True -- True
print 2 -- 2
print "haha" -- "haha"
print 3.2 -- 3.2
print [3,4,3] -- [3,4,3]
print $ map (++"!") ["hey","ho"] -- ["hey!","ho!"]
when
takes a Bool
and an I/O action, and if that value is True
, it returns the same I/O action that we supplied to it. If it’s False
, it returns the return ()
action, which doesn’t do anything.
import Control.Monad
main = do
input <- getLine
when (input == "SWORDFISH") $ do
putStrLn input
sequence
function takes a list of I/O actions and returns an I/O action that will perform those actions one after the other. The result that this I/O action yields will be a list of the results of all the I/O actions that were performed.
main = do
rs <- sequence [getLine, getLine, getLine]
print rs
sequence $ map print [1,2,3,4,5]
1
2
3
4
5
[(),(),(),(),()]
mapM
takes a function and a list, maps the function over the list, and then sequences it.
mapM_
does the same thing, but it throws away the result later.
mapM print [1,2,3]
1
2
3
[(),(),()]
forever
function takes an I/O action and returns an I/O action that just repeats the I/O action it got forever.
import Control.Monad
import Data.Char
main = forever $ do
putStr "Give me some input: "
l <- getLine
putStrLn $ map toUpper l
getContents
reads everything from the standard input until it encounters an end-of-file character. It does lazy I/O.
import Data.Char
main = do
contents <- getContents
putStr $ map toUpper contents
interact
takes a function of type String -> String
as a parameter and returns an I/O action that will take some input, run that function on it, and then print out the function’s result.
main = interact shortLinesOnly
shortLinesOnly :: String -> String
shortLinesOnly = unlines . filter (\line -> length line < 10) . lines
openFile :: FilePath -> IOMode -> IO Handle
type FilePath = String
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
import System.IO
main = do
handle <- openFile "data.txt" ReadMode
contents <- hGetContents handle
putStr contents
hClose handle
// `withFile` makes sure that the file handle gets closed.
withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
import System.IO
main = do
withFile "girlfriend.txt" ReadMode (\handle -> do
contents <- hGetContents handle
putStr contents)
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
import System.IO
import Data.Char
main = do
contents <- readFile "data.txt"
writeFile "data.txt" (map toUpper contents)
import System.IO
main = do
todoItem <- getLine
appendFile "todo.txt" (todoItem ++ "\n")
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withFile name mode f = bracket (openFile name mode)
(\handle -> hClose handle)
(\handle -> f handle)
import System.Environment
import Data.List
main = do
args <- getArgs
progName <- getProgName
putStrLn "The arguments are:"
mapM putStrLn args
putStrLn "The program name is:"
putStrLn progName
- Miran Lipovača: Learn You a Haskell for Great Good!
- O’Sullivan, Goerzen, Stewart: Real World Haskell
- http://www.haskellforall.com/2018/10/detailed-walkthrough-for-beginner.html
- Haskell training: https://github.com/google/haskell-trainings