Created
June 9, 2022 18:45
-
-
Save psilospore/df04258262be9d939fe423db4b81ebcf to your computer and use it in GitHub Desktop.
Conversation on the ReaderT design pattern
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
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