Scotty:连接池作为monad阅读器

时间:2014-03-28 02:49:13

标签: haskell scotty haskell-wai

有数以万亿计的monad教程,包括读者,当你阅读它时似乎都很清楚。但是当你真正需要写作时,它就变成了另一回事。

我从来没有使用过Reader,只是在实践中从未使用过。所以尽管我读到了它,但我不知道该怎么做。

我需要在Scotty中实现一个简单的数据库连接池,这样每个操作都可以使用该池。该池必须是“全局的”,并且可由所有操作功能访问。我读到了这样做的方法是Reader monad。如果还有其他方式,请告诉我。

您能否帮我一下,并正确地说明如何使用Reader完成此操作? 如果我看到自己的例子如何完成,我可能会学得更快。

{-# LANGUAGE OverloadedStrings #-}

module DB where

import Data.Pool
import Database.MongoDB

-- Get data from config
ip = "127.0.0.1"
db = "index"

--Create the connection pool
pool :: IO (Pool Pipe)
pool = createPool (runIOE $ connect $ host ip) close 1 300 5

-- Run a database action with connection pool
run :: Action IO a -> IO (Either Failure a)
run act = flip withResource (\x -> access x master db act) =<< pool

所以上面很简单。我想在每个Scotty动作中使用'run'函数来访问数据库连接池。现在,问题是如何将它包装在Reader monad中以使其可被所有函数访问?我知道'pool'变量必须与所有Scotty动作函数“全局”。

谢谢。

更新

我正在使用完整的代码段更新问题。我在函数链中传递'pool'变量的地方。如果有人可以显示如何更改它以使用monad Reader请。 我不明白该怎么做。

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Network.HTTP.Types
import Web.Scotty
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import Data.Text.Lazy.Internal
import Data.Monoid (mconcat)
import Data.Aeson (object, (.=), encode)
import Network.Wai.Middleware.Static
import Data.Pool
import Database.MongoDB
import Control.Monad.Trans (liftIO,lift)

main = do
  -- Create connection pool to be accessible by all action functions
  pool <- createPool (runIOE $ connect $ host "127.0.0.1") close 1 300 5
  scotty 3000 (basal pool)

basal :: Pool Pipe -> ScottyM ()
basal pool = do
  middleware $ staticPolicy (noDots >-> addBase "static")
  get "/json" (showJson pool)

showJson :: Pool Pipe -> ActionM ()
showJson pool = do
  let run act = withResource pool (\pipe -> access pipe master "index" act) 
  d <- lift $ run $ fetch (select [] "tables")
  let r = either (const []) id d
  text $ LT.pack $ show r

感谢。

更新2

我尝试按照下面建议的方式进行,但它不起作用。 如果有人有任何想法,请。编译错误列表很长,我甚至不知道从哪里开始......

main = do
  pool <- createPool (runIOE $ connect $ host "127.0.0.1") close 1 300 5
  scotty 3000 $ runReaderT basal pool

basal :: ScottyT LT.Text (ReaderT (Pool Pipe) IO) ()
basal = do
  middleware $ staticPolicy (noDots >-> addBase "static")
  get "/json" $ showJson

showJson :: ActionT LT.Text (ReaderT (Pool Pipe) IO) ()
showJson = do
  p <- lift ask
  let rdb a = withResource p (\pipe -> access pipe master "index" a)
  j <- liftIO $ rdb $ fetch (select [] "tables")
  text $ LT.pack $ show j

更新3

感谢 cdk 提供想法并感谢 Ivan Meredith 提供scottyT建议。这个问题也有所帮助:How do I add the Reader monad to Scotty's monad 这是编译的版本。我希望它可以帮助某人并节省一些时间。

import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Encoding as T
import           Data.Text.Lazy (Text)
import           Control.Monad.Reader
import           Web.Scotty.Trans
import           Data.Pool
import           Database.MongoDB

type ScottyD = ScottyT Text (ReaderT (Pool Pipe) IO)
type ActionD = ActionT Text (ReaderT (Pool Pipe) IO)

-- Get data from config
ip = "127.0.0.1"
db = "basal"

main = do
  pool <- createPool (runIOE $ connect $ host ip) close 1 300 5
  let read = \r -> runReaderT r pool
  scottyT 3000 read read basal

-- Application, meaddleware and routes
basal ::  ScottyD ()
basal = do
  get "/" shoot

-- Route action handlers
shoot ::  ActionD ()
shoot = do
  r <- rundb $ fetch $ select [] "computers"
  html $ T.pack $ show r

-- Database access shortcut
rundb :: Action IO a -> ActionD (Either Failure a)
rundb a = do
  pool <- lift ask
  liftIO $ withResource pool (\pipe -> access pipe master db a)

2 个答案:

答案 0 :(得分:8)

我一直在努力弄清楚这个确切的问题。感谢有关此SO问题的提示,以及其他研究,我提出了以下哪些对我有用。你缺少的关键是使用scottyT

毫无疑问,编写runDB有一种更漂亮的方法,但我对Haskell没有太多经验,所以如果你能做得更好,请发帖。

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

更新

我想这更漂亮。

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

答案 1 :(得分:2)

正如您所提到的,使其可访问的方法是将计算包装在Reader monad或更可能是ReaderT转换器中。所以你的run功能(略有改动)

run :: Pool Pipe -> Action IO a -> IO (Either Failure a)
run pool act =
    flip withResource (\x -> access x master db act) =<< pool

变为

run :: Action IO a -> ReaderT (Pool Pipe) IO (Either Failure a)
run act = do
    pool <- ask
    withResource pool (\x -> access x master db act)

ReaderT r m a环境中的计算可以使用r访问ask,而ReaderT似乎凭空捏造!实际上,ReaderT monad只是在整个计算过程中管道Env,而不必担心它。

要运行ReaderT操作,请使用runReaderT :: ReaderT r m a -> r -> m a。因此,您在顶级runReaderT函数上调用scotty以提供PoolrunReaderT将解包ReaderT环境,并在基本monad中返回一个值

例如,评估您的run功能

-- remember: run act :: ReaderT (Pool Pipe) IO (Either Failure a)
runReaderT (run act) pool

但您不希望在runReaderT上使用run,因为它可能是应该共享ReaderT环境的更大计算的一部分。尽量避免在“叶子”计算中使用runReaderT,通常应该尽可能将它称为程序逻辑中的高位。

编辑ReaderReaderT之间的区别在于Reader是monad而ReaderT是monad 转换器< / em>的。也就是说,ReaderTReader行为添加到另一个monad(或monad变换器堆栈)。如果您不熟悉monad变换器,我建议real world haskell - transformers

您有showJson pool ~ ActionM (),并且想要添加Reader环境,可以访问Pool Pipe。在这种情况下,您实际上需要ActionTScottyT变换器而不是ReaderT才能使用scotty包中的函数。

请注意ActionM定义为type ActionM = ActionT Text IO,类似于ScottyM

我没有安装所有必需的库,所以这可能不是类型检查,但它应该给你正确的想法。

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