【问题标题】:Scotty: connection pool as monad readerScotty:连接池作为 monad reader
【发布时间】:2014-05-07 08:26:22
【问题描述】:

包括阅读器在内的 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 操作中使用“运行”功能来访问数据库连接池。现在,问题是如何将它包装在 Reader monad 中以使其可供所有函数访问?我知道对于所有 Scotty 操作函数,“池”变量必须“像全局”。

谢谢。

更新

我正在使用完整代码 sn-p 更新问题。我将“池”变量传递到函数链的位置。如果有人可以展示如何更改它以使用 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)

【问题讨论】:

    标签: haskell scotty haskell-wai


    【解决方案1】:

    我一直在尝试自己找出这个确切的问题。感谢关于这个 SO question 的提示,以及我提出的其他研究,这些对我有用。您缺少的关键位是使用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
    

    【讨论】:

    • 感谢您的回答和 scottyT 的建议。减去 scottyT,我几乎到了这一点。我用 scottyT 试过了,但还是不行。我认为问题出在get 函数中,该函数分别接受并返回旧的ActionM 和ScottyM——而不是阅读器增加的新数据类型。目前我并不担心runDB 功能,它是更容易的部分。我所需要的只是解决阅读器问题并将池传递给处理程序。到目前为止,没有任何效果。几个星期以来,对“全局”变量的简单模拟对我来说变成了一个全局问题......
    • 哦.. 好吧...我想我在对您的帖子发表评论后就明白了。我应该使用来自Web.Scotty.Transget 函数,而不是来自Web.Scotty
    • 我用工作版本更新了问题。我还是不太明白scottyT 参数以及为什么我们两次应用runReaderT?如果您能解释一下,我将不胜感激。再次感谢。
    • 你必须为 ScottyT 做一次,为 ActionT 做一次 - 我认为理论上你可以为两者注入不同的环境。
    • 请注意,这在 Scotty 0.10 中已更改。您现在只使用一个runReaderT,一个在ActionT 中运行。
    【解决方案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 环境中的计算可以使用askReaderT 访问r,这似乎是凭空变出的!实际上,ReaderT monad 只是在整个计算过程中连接Env,您不必担心。

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

    例如,评估您的 run 函数

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

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

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

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

    请注意,ActionM 定义为 type ActionM = ActionT Text IOScottyM 类似。

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

    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
    

    【讨论】:

    • 非常感谢您的回答。我很抱歉,但我不明白。我试过了,但它只是没有在我的脑海中点击。我按照您的建议制作了运行功能,但是如果我不能掌握它,那就没有意义的复制和粘贴。有些例子说我需要使用变压器,有些例子说我只需要阅读器。有什么区别?我将更新我的问题,显示整个代码 sn-p 我将“管道”变量向下传递到函数链。你能告诉我怎么做吗?我花了一个周末试图理解它。我知道它一定很简单,但它就是不点击。
    • 感谢您的更新,但不幸的是它不起作用。我尝试留下类型签名,希望 GHC 会扣除它并给我一些线索,但没有运气。
    猜你喜欢
    • 2023-04-05
    • 1970-01-01
    • 2017-06-22
    • 2018-03-26
    • 1970-01-01
    • 2017-08-16
    • 1970-01-01
    • 1970-01-01
    • 2021-07-18
    相关资源
    最近更新 更多