Haskell Scotty:连接池作为monad读取器

Haskell Scotty:连接池作为monad读取器,haskell,scotty,haskell-wai,Haskell,Scotty,Haskell Wai,有数以万亿计的单子教程,包括读者,当你读到它的时候,它似乎都很清楚。但当你真的需要写作时,这就变成了另一回事 我从未使用过阅读器,只是在实践中从未使用过。所以我不知道该怎么做,尽管我读到了 我需要在Scotty中实现一个简单的数据库连接池,以便每个操作都可以使用该池。该池必须是“全局”的,并且可由所有操作功能访问。我读到这样做的方法是读者单子。如果还有其他方法,请告诉我 你能帮我看看如何正确地使用阅读器吗? 如果我用我自己的例子看看是如何做到的,我可能会学得更快 {-# LANGUAGE Ove

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

我从未使用过阅读器,只是在实践中从未使用过。所以我不知道该怎么做,尽管我读到了

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

你能帮我看看如何正确地使用阅读器吗? 如果我用我自己的例子看看是如何做到的,我可能会学得更快

{-# 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
{-#语言重载字符串}
模块数据库,其中
导入数据池
导入数据库.MongoDB
--从配置中获取数据
ip=“127.0.0.1”
db=“索引”
--创建连接池
池::IO(池管道)
pool=createPool(runIOE$connect$host ip)关闭1300 5
--使用连接池运行数据库操作
运行::操作IO a->IO(故障a)
运行act=flip with resource(\x->access x master db act)=addBase“static”)
获取“/json”$showJson
showJson::ActionT LT.Text(ReaderT(池管道)IO)()
showJson=do
p入口管道主“索引”a)

j正如您所提到的,使其可访问的方法是将您的计算封装在
读取器
monad中,或者更可能是封装在
读取器
转换器中。因此,您的
运行
功能(略有更改)

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

EDIT
Reader
ReaderT
之间的区别在于
Reader
是一个单子,而
ReaderT
是一个单子转换器。也就是说,
ReaderT
Reader
行为添加到另一个monad(或monad转换器堆栈)。如果你不熟悉monad变形金刚,我推荐你

您有
showJson-pool~ActionM()
,您想添加一个
读卡器
环境,可以访问
池管道
。在这种情况下,您实际上需要
ActionT
ScottyT
转换器,而不是
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
base::ScottyT文本(ReaderT(池管)IO)()
基本的
中间件$staticPolicy(…)
获取“/json”showJson
showJson::ActionT文本(ReaderT(池管道)IO)()
showJson=do
池访问(p主“索引法”)

d我一直在试图自己解决这个确切的问题。多亏了这个问题的提示和其他研究,我想出了以下对我有用的方法。你缺少的关键是使用
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")
键入MCScottyM=ScottyT TL.Text(读取(池管)IO)
键入MCActionM=ActionT TL.Text(ReaderT(池管道)IO)
main::IO()
main=do
池\r->runReaderT r p
应用程序::MCScottyM()
app=do
中间件$staticPolicy(noDots>->addBase“public”)
获取“/”$do
p MCAction M(故障a中的任一个)
runDB a=(提升请求)>>=(\p->liftIO$withResource p(\pipe->access pipe master“botland”a))
数据源::操作IO[文档]
dataSources=rest=MCActionM(故障a)
runDB a=do

p非常感谢你的回答。很抱歉,我不明白。我试过了,但它在我的脑海中没有点击。我按照你的建议创建了run函数,但如果我不能掌握它,它是没有意义的复制和粘贴。一些示例说我需要使用变压器,一些示例说我只需要阅读器。有什么区别吗?我将更新我的问题显示了我在函数链中传递“pipe”变量的整个代码段。你能告诉我怎么做吗?我花了一个周末试图理解它。我知道它一定很简单,但它就是不点击。感谢你的更新,但不幸的是它不起作用。我试着不使用类型签名,希望GHC能我会扣除它并给我一些线索,但没有运气。谢谢你的答案和scottyT的建议。我几乎达到了这一点,减去scottyT。我用scottyT尝试了它,但仍然不起作用。我认为问题在于
get
函数,它分别接受并返回旧的ActionM和ScottyM,而不是新的数据类型a读者的建议。目前我并不担心
runDB
函数,它是比较容易的部分。我所需要的只是解决读者的问题并将池传递给处理程序。到目前为止,一切都不起作用。简单模拟“全局“数周来,变量变成了我的一个全球性问题……哦。。好。。。我想我在你的帖子上打了评论后就得到了。我应该从
Web.Scotty.Trans
中使用
get
函数,而不是从
Web.Scotty
中。我用工作版本更新了问题。我仍然不太理解
scottyT
参数,以及为什么我们应用
runReaderT
两次?如果你能解释一下,我会非常感激的。再次感谢您。您必须为Scotty和ActionT分别执行一次操作-理论上,我认为您可以为两者注入不同的环境。请注意,这在Scotty 0.10中发生了变化。您现在只使用一个
runReaderT
,即在
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)
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)
-- remember: run act :: ReaderT (Pool Pipe) IO (Either Failure a)
runReaderT (run act) pool
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
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