Monads and IO
maybePlus :: Maybe Int -> Maybe Int -> Maybe Int
maybePlus ma mb = case ma of
Nothing -> Nothing
Just a -> case mb of
Nothing -> Nothing
Just b -> Just (a + b)
π₯Ίππ Awkward pattern matching
andThen :: Maybe Int -> (Int -> Maybe Int) -> Maybe Int
andThen ma f = case ma of
Nothing -> Nothing
Just x -> f x
maybePlus :: Maybe Int -> Maybe Int -> Maybe Int
maybePlus ma mb = andThen ma (\a -> andThen mb (\b -> Just (a + b)))
π Ordinary helper function
π Refactoring
andThen :: Maybe a -> (a -> Maybe b) -> Maybe b
andThen ma f = case ma of
Nothing -> Nothing
Just x -> f x
eitherPlus :: Either String Int -> Either String Int -> Either String Int
eitherPlus ea eb = case ea of
Left err -> Left err
Right a -> case eb of
Left err -> Left err
Right b -> Right (a + b)
π₯Ίππ Awkward pattern matching strikes again
andThen :: Either String Int
-> (Int -> Either String Int)
-> Either String Int
andThen ea f = case ea of
Left err -> Left err
Right x -> f x
eitherPlus :: Either String Int -> Either String Int -> Either String Int
eitherPlus ea eb = andThen ea (\a -> andThen eb (\b -> Right (a + b)))
π Ordinary helper function
π Refactoring
andThen :: Either e a
-> (a -> Either e b)
-> Either e b
andThen ea f = case ea of
Left err -> Left err
Right x -> f x
listPlus :: [Int] -> [Int] -> [Int]
listPlus la lb = case la of
[] -> []
a : as -> case lb of
[] -> []
bs -> map (+ a) bs ++ listPlus as bs
π₯Ίππ Even more awkward pattern matching
andThen :: [Int] -> (Int -> [Int]) -> [Int]
andThen l f = case l of
[] -> []
x : xs -> f x ++ andThen xs f
listPlus :: [Int] -> [Int] -> [Int]
listPlus la lb = andThen la (\a -> andThen lb (\b -> [a + b]))
π Ordinary helper function
π Refactoring
andThen :: [a] -> (a -> [b]) -> [b]
andThen l f = case l of
[] -> []
x : xs -> f x ++ andThen xs f
π The function became slower after refactoring!
andThen :: Maybe a -> (a -> Maybe b) -> Maybe b
andThen :: Either e a -> (a -> Either e b) -> Either e b
andThen :: [a] -> (a -> [b]) -> [b]
π Very similar type signatures
maybePlus ma mb = andThen ma (\a -> andThen mb (\b -> Just (a + b)))
eitherPlus ea eb = andThen ea (\a -> andThen eb (\b -> Right (a + b)))
listPlus la lb = andThen la (\a -> andThen lb (\b -> [a + b]))
π Almost the same implementations
π Monad is a generalization of the "andThen" pattern
We need:
maybePlus ma mb = andThen ma (\a -> andThen mb (\b -> Just (a + b)))
eitherPlus ea eb = andThen ea (\a -> andThen eb (\b -> Right (a + b)))
listPlus la lb = andThen la (\a -> andThen lb (\b -> [a + b]))
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
π§βπ What we can already learn from the definition
π« What we can't see from the above
instance Monad Maybe where
return :: a -> Maybe a
return x = Just x
(>>=) :: Maybe a
-> (a -> Maybe b)
-> Maybe b
Nothing >>= _ = Nothing
Just x >>= f = f x
βΉοΈ Maybe
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
instance Monad (Either e) where
return :: a -> Either e a
return x = Right x
(>>=) :: Either e a
-> (a -> Either e b)
-> Either e b
Left e >>= _ = Left e
Right x >>= f = f x
instance Monad [] where
return :: a -> [a]
return x = [x]
(>>=) :: [a] -> (a -> [b]) -> [b]
l >>= f = concatMap f l
βΉοΈ Either
βΉοΈ List
instance Monad Maybe where
return :: a -> Maybe a
return x = Just x
...
π Correct Monad instance for Maybe
Left identity
Right identity
Associativity
instance Monad Maybe where
return :: a -> Maybe a
return x = Nothing
...
π Incorrect Monad instance for Maybe
andThen :: Maybe a -> (a -> Maybe b) -> Maybe b
andThen :: Either e a -> (a -> Either e b) -> Either e b
andThen :: [a] -> (a -> [b]) -> [b]
π Similar chaining functions
(>>=) :: Monad m => m a -> (a -> m b) -> m b
π Similar constructors
Just :: a -> Maybe a
Right :: a -> Either e a
(:[]) :: a -> [a] -- robot monkey operator
return :: Monad m => a -> m a
maybePlus :: Maybe Int -> Maybe Int -> Maybe Int
maybePlus ma mb = andThen ma (\a -> andThen mb (\b -> Just (a + b)))
0οΈβ£ Starting with the basics
maybePlus :: Maybe Int -> Maybe Int -> Maybe Int
maybePlus ma mb = ma `andThen` (\a -> mb `andThen` (\b -> Just (a + b)))
maybePlus :: Maybe Int -> Maybe Int -> Maybe Int
maybePlus ma mb = ma `andThen` \a -> mb `andThen` \b -> Just (a + b)
1οΈβ£ Using infix form of andThen
2οΈβ£ Removing redundant ()
maybePlus :: Maybe Int -> Maybe Int -> Maybe Int
maybePlus ma mb = ma >>= \a -> mb >>= \b -> return (a + b)
monadPlus :: Monad m => m Int -> m Int -> m Int
monadPlus ma mb = ma >>= \a -> mb >>= \b -> return (a + b)
3οΈβ£ Replacing `andThen` with >>=Β and Just with return
4οΈβ£Generalizing the type and changing the name
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
class Applicative m => Monad m where
return :: a -> m a
return = pure
(>>=) :: m a -> (a -> m b) -> m b
βΉοΈ Monad is a part of FAMily
π©βπ¬ Everyone uses pure instead of return nowadays
map :: (a -> b) -> [a] -> [b]
π Pure functions β functions without side-effects that depend only on their explicit input arguments.
getLine :: String
π€ How would a function that reads a line would look like?
ghci> getTwoLines !! 1
???????
π€π€ How about reading two lines??
getTwoLines :: [String]
getTwoLInes = [getLine, getLine]
π€π€π€ How this should work???
π‘ We can't have side effects in Haskell without changing at least something. Otherwise, we'll lose the remainings of our sanity while working with the language.
data IO a = ...
βΉοΈ Opaque data type IO
βΉοΈ Functions with side-effects
getLine :: IO String
π©βπ¬ getLine is a function that returns a value of type String and also performs some side-effects
βΉοΈ IO has the Monad instance
instance Monad IO where
return :: a -> IO a
(>>=) :: IO a -> (a -> IO b) -> IO b
...
π©βπ¬ The Monad instance for IO allows to chain effectful actions
getTwoLines :: IO [String]
getTwoLines =
getLine >>= \line1 -> getLine >>= \line2 -> pure [line1, line2]
String
IO String
βΉοΈ A helper function to print to the terminal
putStrLn :: String -> IO ()
π©βπ¬ putStrLn is a function that takes a value of type String, prints it the terminal and doesn't return any meaningful value.
ghci> () -- a unit type
()
ghci> :t ()
() :: ()
ghci> putStrLn "Hi! π"
Hi! π
ghci> :t getLine
getLine :: IO String
ghci> getLine
Alice
"Alice"
ghci> fmap length getLine
Bob
3
module Main where
main :: IO ()
main = ... your program goes here ...
π©βπ¬ Haskell program starts with the main function in the module Main
module Main where
main :: IO ()
main = putStrLn "Hello, world!"
βWe want to print two different lines
putStrLn :: String -> IO ()
π©βπ¬ We use the bind operator (>>=) and ignore the putStrLn resutl
module Main where
main :: IO ()
main = putStrLn "Hello, Alice!" >>= \_ -> putStrLn "Hello, Bob!"
(>>) :: Monad m => m a -> m b -> m b
action1 >> action2 = action1 >>= \_ -> action2
βΉοΈ A helper function
main :: IO ()
main = putStrLn "Hello, Alice!" >> putStrLn "Hello, Bob!"
βΉοΈ Slightly shorter version
π do-notation β syntax sugar for >>=,Β >> and let-in
example1 =
fun1 >>= \result -> fun2 result
example1 = do
result <- fun1
fun2 result
βΉοΈ >>= rule
example1 = fun1 >>= fun2
Eta-reduced example1 with >>=
βΉοΈ >>Β rule
example2 = fun1 >> fun2
example2 = do
fun1
fun2
βΉοΈ let-in rule
example3 = let x = f y in fun x
example3 = do
let x = f y
fun x
βοΈ Write a program that reads a line and prints its reverse
main :: IO ()
main =
getLine >>= \line ->
let rev = reverse line in
putStrLn rev
main :: IO ()
main = do
line <- getLine
let rev = reverse line
putStrLn rev
π Classic Monad Style
let line = getLine
rev <- reverse line
π do-Style
π Cabal β format for describing structure of Haskell packages.
my-project/
βββ app/
β βββ Main.hs
βββ src/
β βββ MyModule.hs
β βββ AnotherModule.hs
βββ my-project.cabal
cabal-version: 2.4
name: my-project
version: 1.0.0.0
library
hs-source-dirs: src
exposed-modules: MyModule
AnotherModule
build-depends: base ^>= 4.14
executable my-project
hs-source-dirs: app
main-is: Main.hs
build-depends: my-project
, base
π A Haskell package
π Files with the .cabalΒ file extension describe the package.Β
π cabal-install β a Haskell build tool that works with Cabal packages.
module Main (main) where
import qualified MyModule
main :: IO ()
main = MyModule.main
library
hs-source-dirs: src
exposed-modules: MyModule
build-depends: base ^>= 4.14
, containers ^>= 0.6
, text ^>= 1.2
, time ^>= 1.9
π The build-depends field in the .cabal file specifies external Haskell packages and their versions you want to use.
π¦ base β standard Haskell library
π¦ containers β Map and Set data structures
π¦ text β efficient UTF-16 strings (UTF-8 since version 2.0)
π¦ bytestring β byte arrays
π¦ time β time, clocks and calendar data types and functions
π¦ aeson β JSON parsing
βΉοΈ Hackage has 16K+ packages
readFile :: FilePath -> IO Text
countWords :: Text -> IO (Map Text Int)
printWord :: Map Text Int -> IO ()
π©βπ¬ Haskell allows controlling side effects. Separate pure logic from effectful computations and require IO only when needed.
βοΈ Write a program that finds the most frequent word in a file
π’ Imperative style
π€Έ Functional style
readFile :: FilePath -> IO Text
countWords :: Text -> Map Text Int
findFrequent :: Map Text Int -> Maybe (Text, Int)
displayResult :: Maybe (Text, Int) -> Text
putStrLn :: Text -> IO ()