Skip to content

Instantly share code, notes, and snippets.

@psilospore
Created June 9, 2022 18:45
Show Gist options
  • Save psilospore/df04258262be9d939fe423db4b81ebcf to your computer and use it in GitHub Desktop.
Save psilospore/df04258262be9d939fe423db4b81ebcf to your computer and use it in GitHub Desktop.
Conversation on the ReaderT design pattern
Alright I was trying to find that article but it has way more details then I remember. Here’s what we basically do:
We have a App Monad or “Context”
newtype App a = App {unApp :: ReaderT Environment IO a}
So you can see the IO thing in there but you’ll notice there’s something called ReaderT and Environment. I’ll get to those.
Now when you mess around with Haskell you might use functions in IO e.g.
foo :: IO ()
foo = do
putStrLn "hi"
putStrLn "don't use this function production"
But we want to access things from our database and log things.
We could do this
foo :: DBConnection -> Logger -> IO ()
foo dbConnection logger = do
logger "hi"
users <- Database.select dbConnection "select * from users"
logger users
But that’s annoying to add to every single function: We could put that stuff into a record.
-- | The environment that is accessible within the `App` monad.
data Environment = Environment
{ logger :: Logger
, dbConnectionPool :: DBConnectionPool
}
Environment is like our dependencies or whatever we need to boot our application.
foo :: Environment -> IO ()
foo Environment {dbConnection, logger} = do
logger "hi"
users <- Database.select dbConnection "select * from users"
logger users
So there’s this thing Reader which is basically like an a -> b
So Reader Int String is like Int -> String
ReaderT Environment (IO a) (ignore the t for now) is like Environment -> IO a
foo :: Reader Environment (IO ())
foo = do
Environment {dbConnection, logger} <- Reader.asks -- Notice that I can use this function asks to get the env now instead
logger "hi"
users <- Database.select dbConnection "select * from users"
logger users
This is a fairly common pattern we’ll just call this App or something like that
newtype App a = App {unApp :: ReaderT Environment IO a}
We will also make it have a Monad instance so we can use do notation, and maybe other instances.
prodReadyFoo :: App ()
foo = do
Environment {dbConnection, logger} <- Reader.asks -- Notice that I can use this function asks to get the env now instead
logger "hi"
users <- Database.select dbConnection "select * from users"
logger users
So most of our functions that needs some type of effect use App
That might have been a ton of information and it does take a bit of time to get used to it but once you’re familiar with the pattern it feels natural.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment