我正在使用scotty
and开发 Haskell 服务器persistent
。许多处理程序需要访问数据库连接池,所以我已经开始在整个应用程序中传递池,以这种方式:
main = do
runNoLoggingT $ withSqlitePool ":memory:" 10 $ \pool ->
liftIO $ scotty 7000 (app pool)
app pool = do
get "/people" $ do
people <- liftIO $ runSqlPool getPeople pool
renderPeople people
get "/foods" $ do
food <- liftIO $ runSqlPool getFoods pool
renderFoods food
wheregetPeople
和是分别返回和getFoods
的适当persistent
数据库操作。[Person]
[Food]
一段时间后,在池上调用liftIO
和的模式runSqlPool
变得令人厌烦——如果我可以将它们重构为一个函数,就像 Yesod 的那样runDB
,它只会接受查询并返回适当的类型,那不是很好。我写这样的东西的尝试是:
runDB' :: (MonadIO m) => ConnectionPool -> SqlPersistT IO a -> m a
runDB' pool q = liftIO $ runSqlPool q pool
现在,我可以这样写:
main = do
runNoLoggingT $ withSqlitePool ":memory:" 10 $ \pool ->
liftIO $ scotty 7000 $ app (runDB' pool)
app runDB = do
get "/people" $ do
people <- runDB getPeople
renderPeople people
get "/foods" $ do
food <- runDB getFoods
renderFoods food
除了 GHC 抱怨:
Couldn't match type `Food' with `Person'
Expected type: persistent-2.1.1.4:Database.Persist.Sql.Types.SqlPersistT
IO
[persistent-2.1.1.4:Database.Persist.Class.PersistEntity.Entity
Person]
Actual type: persistent-2.1.1.4:Database.Persist.Sql.Types.SqlPersistT
IO
[persistent-2.1.1.4:Database.Persist.Class.PersistEntity.Entity
Food]
In the first argument of `runDB', namely `getFoods'
似乎 GHC 是在说实际上类型runDB
变得专业化了。但是,函数是如何runSqlPool
定义的呢?它的类型签名看起来与我的相似:
runSqlPool :: MonadBaseControl IO m => SqlPersistT m a -> Pool Connection -> m a
但它可以与返回许多不同类型的数据库查询一起使用,就像我最初所做的那样。我认为我在这里对类型有一些基本的误解,但我不知道如何找出它是什么!任何帮助将不胜感激。
编辑:
在 Yuras 的建议下,我添加了这个:
type DBRunner m a = (MonadIO m) => SqlPersistT IO a -> m a
runDB' :: ConnectionPool -> DBRunner m a
app :: forall a. DBRunner ActionM a -> ScottyM ()
-XRankNTypes
这是 typedef所必需的。但是,编译器错误仍然相同。
编辑:
评论者的胜利。这允许代码编译:
app :: (forall a. DBRunner ActionM a) -> ScottyM ()
我很感激,但仍然很困惑!