There are trillions of monad tutorial including the reader and it seems all clear when you read about it. But when you actually need to write, it becomes a different matter.
As you've alluded, the way to make it accessable is to wrap your computations in the Reader
monad or more likely the ReaderT
transformer. So your run
function (changed slightly)
run :: Pool Pipe -> Action IO a -> IO (Either Failure a)
run pool act =
flip withResource (\x -> access x master db act) =<< pool
becomes
run :: Action IO a -> ReaderT (Pool Pipe) IO (Either Failure a)
run act = do
pool <- ask
withResource pool (\x -> access x master db act)
Computations inside a ReaderT r m a
environment can access the r
using ask
and ReaderT
seemingly conjures it out of thin air! In reality, the ReaderT
monad is just plumbing the Env
throughout the computation without you having to worry about it.
To run a ReaderT
action, you use runReaderT :: ReaderT r m a -> r -> m a
. So you call runReaderT
on your top level scotty
function to provide the Pool
and runReaderT
will unwrap the ReaderT
environment and return you a value in the base monad.
For example, to evaluate your run
function
-- remember: run act :: ReaderT (Pool Pipe) IO (Either Failure a)
runReaderT (run act) pool
but you wouldn't want to use runReaderT
on run
, as it is probably part of a larger computation that should also share the ReaderT
environment. Try to avoid using runReaderT
on "leaf" computations, you should generally call it as high up in the program logic as possible.
EDIT: The difference between Reader
and ReaderT
is that Reader
is a monad while ReaderT
is a monad transformer. That is, ReaderT
adds the Reader
behaviour to another monad (or monad transformer stack). If you're not familiar with monad transformers I'd recommend real world haskell - transformers.
You have showJson pool ~ ActionM ()
and you want to add a Reader
environment with access to a Pool Pipe
. In this case, you actually need ActionT
and ScottyT
transformers rather than ReaderT
in order to work with functions from the scotty
package.
Note that ActionM
is defined type ActionM = ActionT Text IO
, similarly for ScottyM
.
I don't have all the necessary libraries installed, so this might not typecheck, but it should give you the right idea.
basal :: ScottyT Text (ReaderT (Pool Pipe) IO) ()
basal = do
middleware $ staticPolicy (...)
get "/json" showJson
showJson :: ActionT Text (ReaderT (Pool Pipe) IO) ()
showJson = do
pool <- lift ask
let run act = withResource pool (\p -> access p master "index act)
d <- liftIO $ run $ fetch $ select [] "tables"
text . TL.pack $ either (const "") show d
I've been trying to figure out this exact problem myself. Thanks to hints on this SO question, and other research I've come up with the following which works for me. The key bit you were missing was to use scottyT
No doubt there is a prettier way to write runDB but I don't have much experience in Haskell, so please post it if you can do better.
type MCScottyM = ScottyT TL.Text (ReaderT (Pool Pipe) IO)
type MCActionM = ActionT TL.Text (ReaderT (Pool Pipe) IO)
main :: IO ()
main = do
pool <- createPool (runIOE $ connect $ host "127.0.0.1") close 1 300 5
scottyT 3000 (f pool) (f pool) $ app
where
f = \p -> \r -> runReaderT r p
app :: MCScottyM ()
app = do
middleware $ staticPolicy (noDots >-> addBase "public")
get "/" $ do
p <- runDB dataSources
html $ TL.pack $ show p
runDB :: Action IO a -> MCActionM (Either Failure a)
runDB a = (lift ask) >>= (\p -> liftIO $ withResource p (\pipe -> access pipe master "botland" a))
dataSources :: Action IO [Document]
dataSources = rest =<< find (select [] "datasources")
I guess this a bit more pretty.
runDB :: Action IO a -> MCActionM (Either Failure a)
runDB a = do
p <- lift ask
liftIO $ withResource p db
where
db pipe = access pipe master "botland" a