Created
July 21, 2021 07:40
-
-
Save ecthiender/5484b67c1251a20af00cf85f9cdead70 to your computer and use it in GitHub Desktop.
Example of how to use Reader monad
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE FlexibleContexts #-} | |
module Main where | |
import Control.Applicative (liftA2) | |
import Control.Monad.Reader (MonadIO, MonadReader, ReaderT, ask, | |
runReaderT) | |
{- | Let's say we have a deeply nested call stack. There are many functions | |
calling other functions. | |
In the below example, the call stack looks like - | |
main -> runApp -> doSomethingSpecial -> doFooAndBar -> doFoo | |
And other than doFoo, the config parameter is not used by any other function | |
call. They just plumb through the config | |
-} | |
main :: IO () | |
main = do | |
config <- readConfig | |
res <- runApp config | |
putStrLn $ "Final result: " ++ res | |
data Config = Config | |
{ configFoo :: Int | |
, configBar :: String | |
} deriving Show | |
readConfig :: IO Config | |
readConfig = undefined | |
runApp :: Config -> IO String | |
runApp config = do | |
doSomethingSpecial config | |
doSomethingSpecial :: Config -> IO String | |
doSomethingSpecial config = do | |
r1 <- doFooAndBar config | |
r2 <- doBar config | |
pure $ r1 ++ r2 | |
doFooAndBar :: Config -> IO String | |
doFooAndBar c = liftA2 (++) (doFoo c) (doBar c) | |
doFoo :: Config -> IO String | |
doFoo c = pure $ show (configFoo c + 1) | |
doBar :: Config -> IO String | |
doBar = undefined | |
{- | We can use the Reader (ReaderT) monad pattern, to refactor this. So all | |
function calls operate with a type `ReaderT ....` but none of them have to | |
explictly pass down the parameter | |
-} | |
main' :: IO () | |
main' = do | |
config <- readConfig | |
res <- runReaderT runApp' config | |
putStrLn $ "Final result: " ++ res | |
runApp' :: ReaderT Config IO String | |
runApp' = doSomethingSpecial' | |
doSomethingSpecial' :: ReaderT Config IO String | |
doSomethingSpecial' = do | |
r1 <- doFooAndBar' | |
r2 <- doBar' | |
pure $ r1 ++ r2 | |
doFooAndBar' :: ReaderT Config IO String | |
doFooAndBar' = liftA2 (++) doFoo' doBar' | |
doFoo' :: ReaderT Config IO String | |
doFoo' = do | |
config <- ask | |
pure $ show (configFoo config + 1) | |
doBar' :: ReaderT Config IO String | |
doBar' = undefined | |
{- | Then to make our functions more flexible (polymorphic), we can take away the | |
concrete `ReaderT` monad and just use typeclass constraints. Interestingly, | |
below, nothing except the type signature changes! All implementations are | |
exactly like above! | |
-} | |
main'' :: IO () | |
main'' = do | |
config <- readConfig | |
res <- runReaderT runApp'' config | |
putStrLn $ "Final result: " ++ res | |
runApp'' :: (MonadReader Config m, MonadIO m) => m String -- You can keep this type or fix this to a more concrete type like `ReaderT Config IO String` | |
runApp'' = doSomethingSpecial'' | |
doSomethingSpecial'' :: (MonadReader Config m, MonadIO m) => m String | |
doSomethingSpecial'' = do | |
r1 <- doFooAndBar'' | |
r2 <- doBar'' | |
pure $ r1 ++ r2 | |
doFooAndBar'' :: (MonadReader Config m, MonadIO m) => m String | |
doFooAndBar'' = liftA2 (++) doFoo'' doBar'' | |
doFoo'' :: (MonadReader Config m, MonadIO m) => m String | |
doFoo'' = do | |
config <- ask | |
pure $ show (configFoo config + 1) | |
doBar'' :: (MonadReader Config m, MonadIO m) => m String | |
doBar'' = undefined |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment