Migrating code to the Reader Monad

The Reader Monad is one of the famous trio: Reader/Writer/State. They are all really powerful abstractions and fit specific scenarios like being able to read global configuration, being able to trace the execution of the program or being able to run stateful computations.

In this post, I will share how to evolve a program using explicit configuration parameter into a program that uses the Reader Monad.

The base program

Imagine we wrote a Haskell program which:

  1. Establishes a connection to the memcache server using the memcache package
  2. Reads and prints the current value for the counter key
  3. Stores "1" under the counter key
  4. Reads and prints the current value for the above key
  5. Stores "2" under the counter key
  6. Reads and prints the current value for the above key

Here are the necessary imports:

{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified Database.Memcache.Client as MemcacheClient
import qualified Database.Memcache.Types as MemcacheTypes
import           Data.Maybe (Maybe(..))

A type alias for memcache result:

type MemcacheResult = Maybe (MemcacheTypes.Value, MemcacheTypes.Flags, MemcacheTypes.Version)

A set of methods to establish the connection, store values in and retrieve from the server:

connection :: IO MemcacheClient.Client
connection = MemcacheClient.newClient [MemcacheClient.def] MemcacheClient.def

-- Example: set connection "counter" "1"
set :: MemcacheClient.Client -> MemcacheTypes.Key -> MemcacheTypes.Value -> IO MemcacheTypes.Version
set connection key value = MemcacheClient.set connection key value 0 0

-- Example: get connection "counter"
get :: MemcacheClient.Client -> MemcacheTypes.Key -> IO MemcacheResult
get = MemcacheClient.get

And finally - the actual program:

main :: IO ()
main = do
  conn <- connection

  get conn "counter" >>= print
  set conn "counter" "1"
  get conn "counter" >>= print
  set conn "counter" "2"
  get conn "counter" >>= print

  return ()
  
-- First run:
-- Nothing
-- Just ("1",0,1)
-- Just ("2",0,2)
--
-- Consequent run:
-- Just ("2",0,2)
-- Just ("1",0,3)
-- Just ("2",0,4)

The transition to the Reader Monad

After installing the mtl package we can make use of the Reader Monad to make the above functions a bit easier to use.

We start by adding some additional imports:

import Data.Function (flip)
import Control.Monad.Reader (ReaderT, runReaderT, ask)
import Control.Monad.IO.Class (liftIO)

Next, we change the get and set helper functions:

-- Example: set "counter" "1"
set :: MemcacheTypes.Key -> MemcacheTypes.Value -> ReaderT MemcacheClient.Client IO MemcacheTypes.Version
set key value = do
  connection <- ask
  liftIO $ MemcacheClient.set connection key value 0 0
  
-- Example: get "counter"
get :: MemcacheTypes.Key -> ReaderT MemcacheClient.Client IO MemcacheResult
get key = do
  connection <- ask
  liftIO $ MemcacheClient.get connection key

and finally we change the main function to call runReaderT with the connection used as the configuration/environment being read with ask calls:

main :: IO ()
main = do
  conn <- connection

  flip runReaderT conn $ do
    get "counter" >>= liftIO . print
    set "counter" "1"
    get "counter" >>= liftIO . print
    set "counter" "2"
    get "counter" >>= liftIO . print

  return ()

-- First run:
-- Nothing
-- Just ("1",0,1)
-- Just ("2",0,2)
--
-- Consequent run:
-- Just ("2",0,2)
-- Just ("1",0,3)
-- Just ("2",0,4)

Full code listing: src/Main.hs

Summary

With these small changes, we managed to transform the base program to pass connection to helper functions from one place thanks to how the reader monad works. The actual behavior of the program didn't change.

In the real world, one would pass a record full of configuration options — database host, api endpoints, environment type etc. — to the runReaderT call.

Do you find the second version better or do you think it's worse? Let me know on twitter - @maciejsmolinski.

Monads by example

Running :i Monad in ghci yields the following:

class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  (>>) :: m a -> m b -> m b
  return :: a -> m a
  fail :: String -> m a

It also lists available instances of the Monad type class:

instance Monad m => Monad (WrappedMonad m)
  -- Defined in ‘Control.Applicative’
instance Monad (Either e) -- Defined in ‘Data.Either’
instance Monad [] -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Monad IO -- Defined in ‘GHC.Base’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Monoid a => Monad ((,) a) -- Defined in ‘GHC.Base’

Concrete type signatures

To understand similarities and differences between various instances of the Monad type class we will take a look at the type signatures of the return, >>= and >> functions for List, Maybe and Either types:

-- Generic
return :: a -> m a

-- List
return :: a -> [a]

-- Maybe
return :: a -> Maybe a

-- Either
return :: a -> Either l a
-- Generic
(>>=) :: m a -> (a -> m b) -> m b

-- List
(>>=) :: [a] -> (a -> [b]) -> [b]

-- Maybe
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b

-- Either
(>>=) :: Either a b -> (b -> Either a c) -> Either a c
-- Generic
(>>) :: m a -> m b -> m b

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

-- Maybe
(>>) :: Maybe a -> Maybe b -> Maybe b

-- Either
(>>) :: Either a b -> Either a c -> Either a c

Using return with different types

Do you remember how we said "pure lifts a value to a given type" in the previous note? return is a function doing exactly the same thing for the instances of the Monad type class:

-- List
(return 2 :: [Integer]) == [2]
-- Maybe
(return 2 :: Maybe Integer) == Just 2
-- Either
(return 2 :: Either a Integer) == Right 2

Interactive Examples:


Using >>= with different types

>>= also known as bind helps us run monadic actions (values) in sequence by composing them. A value produced by the first one will be passed as an argument to the second.

After actions are defined we will use >>= to sequentially execute our program:

addOne :: (Monad m, Num a) => a -> m a
addOne a = return (a + 1)
-- List
[ ]    >>= addOne == [ ]
[1]    >>= addOne == [2]
[1, 2] >>= addOne == [2, 3]

[ ]    >>= addOne >>= addOne == [ ]
[1]    >>= addOne >>= addOne == [3]
[1, 2] >>= addOne >>= addOne == [3, 4]
-- Maybe
Nothing >>= addOne == Nothing
Just 1  >>= addOne == Just 2

Nothing >>= addOne >>= addOne == Nothing
Just 1  >>= addOne >>= addOne == Just 3
-- Either
Left  1 >>= addOne == Left  1
Right 1 >>= addOne == Right 2

Left  1 >>= addOne >>= addOne == Left  1
Right 1 >>= addOne >>= addOne == Right 3

Iteractive Examples:


Using >> with different types

Similar to the bind operator >> allows us to sequentially compose two actions by ignoring the value produced by the first.

-- List
[ ] >> [ ] == [ ]
[ ] >> [2] == [ ]
[1] >> [ ] == [ ]
[1] >> [2] == [2]
-- Maybe
Nothing >> Nothing == Nothing
Nothing >> Just 2  == Nothing
Just 1  >> Nothing == Nothing
Just 1  >> Just 2  == Just 2
-- Either
Left  1 >> Left  2 == Left  1
Left  1 >> Right 2 == Left  1
Right 1 >> Left  2 == Left  2
Right 1 >> Right 2 == Right 2

Interactive Examples:


I was pretty intrigued when I saw how these methods work. After reading so much about Monads on the internet I was pretty scared of the concept but then — after I started using them — it turned out they aren't as scary as I originally thought.

Let me know on twitter what was your experience - @maciejsmolinski.

Applicatives by example

Running :i Applicative in ghci yields the following:

class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
  GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c
  (*>) :: f a -> f b -> f b
  (<*) :: f a -> f b -> f a

It also lists available instances of the Applicative type class:

instance Applicative (Either e) -- Defined in ‘Data.Either’
instance Applicative [] -- Defined in ‘GHC.Base’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Applicative IO -- Defined in ‘GHC.Base’
instance Applicative ((->) a) -- Defined in ‘GHC.Base’
instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’

Concrete type signatures

To understand similarities and differences between various instances of the Applicative type class we will take a look at the type signatures of the pure, <*> and liftA2 functions for List, Maybe and Either types:

-- Generic
pure :: a -> f a

-- List
pure :: a -> [a]

-- Maybe
pure :: a -> Maybe a

-- Either
pure :: a -> Either l a
-- Generic
(<*>) :: f (a -> b) -> f a -> f b

-- List
(<*>) :: [(a -> b)] -> [a] -> [b]

-- Maybe
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b

-- Either
(<*>) :: Either l (a -> b) -> Either l a -> Either l b
-- Generic
liftA2 :: (a -> b -> c) -> f a -> f b -> f c

-- List
liftA2 :: (a -> b -> c) -> [a] -> [b] -> [c]

-- Maybe
liftA2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c

-- Either
liftA2 :: (a -> b -> c) -> Either l a -> Either l b -> Either l c

Using pure with different types

pure lifts a value to a given type. The type can be inferred or specified with a type annotation like in the examples below:

-- List
(pure 2 :: [Integer]) == [2]
-- Maybe
(pure 2 :: Maybe Integer) == Just 2
-- Either
(pure 2 :: Either a Integer) == Right 2

Interactive Examples:


Using <*> with different types

In order to use <*> we need to define some functions we want to work with. After they are defined we will apply them to some instances of the Applicative type class:

addOne :: Num a => a -> a
addOne a = a + 1

addTwo :: Num a => a -> a
addTwo a = a + 2
-- List
[      ]         <*> [ ]    == [ ]  
[      ]         <*> [2]    == [ ]
[addOne]         <*> [ ]    == [ ]
[addOne]         <*> [2]    == [3]
[addOne, addTwo] <*> [ ]    == [ ]
[addOne, addTwo] <*> [2]    == [3, 4]
[addOne, addTwo] <*> [2, 3] == [3, 4, 4, 5]
-- Maybe
Nothing     <*> Nothing == Nothing
Nothing     <*> Just 2  == Nothing
Just addOne <*> Nothing == Nothing
Just addOne <*> Just 2  == Just 3
-- Either
Left  addOne <*> Left  2 == Left  addOne
Left  addOne <*> Right 2 == Left  addOne
Right addOne <*> Left  2 == Left  2
Right addOne <*> Right 2 == Right 3

Interactive Examples:


Using liftA2 with different types

To use liftA2 we also need to define a function. The function will accept two parameters and return a single value. After the function is defined we will apply it to some instances of the Applicative type class:

add :: Num a => a -> a -> a
add a b = a + b
-- List
import Control.Applicative (liftA2)

liftA2 add [ ]   [ ]   == []
liftA2 add [1]   [ ]   == []
liftA2 add [1]   [2]   == [3]
liftA2 add [1,2] [2]   == [3,4]
liftA2 add [1,2] [2,3] == [3,4,4,5]
-- Maybe
import Control.Applicative (liftA2)

liftA2 add (Nothing) (Nothing) == Nothing
liftA2 add (Nothing) (Just 2)  == Nothing
liftA2 add (Just 1)  (Nothing) == Nothing
liftA2 add (Just 1)  (Just 2)  == Just 3
-- Either
import Control.Applicative (liftA2)

liftA2 add (Left  1) (Left  2) == Left  1
liftA2 add (Left  1) (Right 2) == Left  1
liftA2 add (Right 1) (Left  2) == Left  2
liftA2 add (Right 1) (Right 2) == Right 3

Interactive Examples:


Applicatives are not scary either. They are pretty useful as you can see above. Let me know on twitter if you have any comments - @maciejsmolinski.

In the next post you will learn about Monads.

Functors by example

Running :i Functor in ghci yields the following:

class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a

It also lists available instances of the Functor type class:

instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’

Concrete type signatures

To understand similarities and differences between various instances of the Functor type class we will take a look at the type signatures of the fmap and <$ functions for List, Maybe and Either types:

-- Generic
fmap :: (a -> b) -> f a -> f b

-- List
fmap :: (a -> b) -> [a] -> [b]

-- Maybe
fmap :: (a -> b) -> Maybe a -> Maybe b

-- Either
fmap :: (b -> c) -> Either a b -> Either a c
-- Generic
(<$) :: a -> f b -> f a

-- List
(<$) :: c -> [a] -> [c]

-- Maybe
(<$) :: c -> Maybe a -> Maybe c

-- Either
(<$) :: c -> Either a b -> Either a c

Using fmap with different types

In order to use fmap we need to define a function we want to work with. After it is defined we will apply the function to some instances of the Functor type class:

addOne :: Num a => a -> a
addOne a = a + 1
-- List
fmap addOne [ ]       == [ ]
fmap addOne [1]       == [2]
fmap addOne [1, 2, 3] == [2, 3, 4]
-- Maybe
fmap addOne (Nothing) == Nothing
fmap addOne (Just 2)  == Just 3
-- Either
fmap addOne (Left  0) == Left  0
fmap addOne (Right 2) == Right 3

Interactive Examples:


Using <$ with different types

<$ swaps values in the instance of the Functor typeclass and is originally defined as fmap . const.

-- List
1 <$ [ ]     == [ ]
1 <$ [2]     == [1]
1 <$ [1,2,3] == [1,1,1]
-- Maybe
1 <$ (Nothing) == Nothing
1 <$ (Just 2)  == Just 1
-- Either
1 <$ (Left  0) == Left  0
1 <$ (Right 2) == Right 1

Interactive Examples:


It's not as bad as it looks, or is it? Let me know on twitter - @maciejsmolinski.

In the next post you will learn in a similar way about Applicatives.