Lecture 4

Monads and IO

Monad: The Idea

Maybe, yes? Maybe, no?

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

Either way

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

Multiple combinations

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!

Is there a pattern?

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

What to do when we see a pattern?

1. Nothing

2. Recognize

4. Abstract

5. Put in a library

3. Copy-paste

Monad is a pattern

πŸ“œ Monad is a generalization of the "andThen" pattern

How to generalize? πŸ€”

We need:

  1. Polymorphic andThen
  2. Polymorphic constructor
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]))

Monad

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

  1. Monad is a typeclass
  2. It has two methods
  3. The second method is an operator (it's called bind)
  4. It's a typeclass for type constructors like Maybe and not e.g. Int
  1. Instances implemementations
  2. Laws
  3. How to use this abstraction efficiently
  4. Why it's called Monad

Just a typeclass

Instances

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

Laws

instance Monad Maybe where
    return :: a -> Maybe a
    return x = Just x

    ...

🍏 Correct Monad instance for Maybe

Left identity

Right identity

Associativity

\mathrm{return} \ a\ \gg =\ f \equiv f \ a
m \ \gg = \ \mathrm{return} \equiv m
(m \gg = f) \gg = g \equiv m \gg = (\lambda x \rightarrow f \ x \gg = g)
instance Monad Maybe where
    return :: a -> Maybe a
    return x = Nothing

    ...

πŸ› Incorrect Monad instance for Maybe

Generalizing

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

Refactoring

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

Actually, in Haskell...

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

And that's the Monad!

Real World

Purity πŸ’§

map :: (a -> b) -> [a] -> [b]

πŸ“œ Pure functions β€” functions without side-effects that depend only on their explicit input arguments.

πŸ₯‡ Benefits of pure functions

1. Determinism

4. Composability

2. Easier reasoning

5. Optimizations

6. Parallelism

3. Simpler testing

πŸ’§ Purity + Laziness πŸ¦₯

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???

Conclusion

πŸ’‘ 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.

Input/Output (IO)

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]

Common question

How to get String from IO String?

Concept of IO

String
IO String

How to run IO?

ℹ️ 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

How to run Haskell programs?

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!"

Hello World

Printing twice

❓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

πŸ“œ 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

Example with do

βš™οΈ 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

⛔️ Common mistake #1

let line = getLine
rev <- reverse line

⛔️ Common mistake #2

πŸ– do-Style

Cabal

Cabal

πŸ“œ 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.

Typical main

module Main (main) where

import qualified MyModule

main :: IO ()
main = MyModule.main

build-depends

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.

Common Haskell packages

πŸ“¦ 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

Functional 🌽, Imperative 🐚

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 ()

More sources

Lecture 4: Monads and IO

By Haskell Beginners 2022

Lecture 4: Monads and IO

Fundamentals of Functional Programming and Haskell

  • 2,866