Scotty: connection pool as monad reader

微笑、不失礼 提交于 2019-11-28 18:48:45

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")

Update

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

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!