Dan*_*ter 16 polymorphism haskell scotty
我正在使用scotty和处理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
Run Code Online (Sandbox Code Playgroud)
其中,getPeople和getFoods适合persistent返回数据库操作[Person]和[Food]分别.
调用liftIO和runSqlPool在池上的模式在一段时间后变得令人厌倦 - 如果我可以将它们重构为单个函数(如Yesod的那样runDB,它只会接受查询并返回适当的类型),那不是很好.我写这样的东西的尝试是:
runDB' :: (MonadIO m) => ConnectionPool -> SqlPersistT IO a -> m a
runDB' pool q = liftIO $ runSqlPool q pool
Run Code Online (Sandbox Code Playgroud)
现在,我可以这样写:
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
Run Code Online (Sandbox Code Playgroud)
除了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'
Run Code Online (Sandbox Code Playgroud)
似乎GHC说实际上这种类型在runDB某种程度上变得专业化.但那么函数如何runSqlPool定义?它的类型签名看起来与我的类似:
runSqlPool :: MonadBaseControl IO m => SqlPersistT m a -> Pool Connection -> m a
Run Code Online (Sandbox Code Playgroud)
但它可以用于返回许多不同类型的数据库查询,就像我最初做的那样.我认为这里有一些基本的东西,我对这里的类型有误解,但我不知道如何找出它是什么!任何帮助将不胜感激.
编辑:
在Yuras的建议中,我添加了这个:
type DBRunner m a = (MonadIO m) => SqlPersistT IO a -> m a
runDB' :: ConnectionPool -> DBRunner m a
app :: forall a. DBRunner ActionM a -> ScottyM ()
Run Code Online (Sandbox Code Playgroud)
-XRankNTypestypedef 所需的.但是,编译器错误仍然相同.
编辑:
评论员的胜利.这允许代码编译:
app :: (forall a. DBRunner ActionM a) -> ScottyM ()
Run Code Online (Sandbox Code Playgroud)
对此我感激不尽,但仍然神秘!
Tom*_*lis 20
似乎GHC说实际上runDB的类型在某种程度上变得专业化.
你的猜测是正确的.你原来的类型是app :: (MonadIO m) => (SqlPersistT IO a -> m a) -> ScottyM ().这意味着您runDB的类型参数SqlPersistT IO a -> m a可以在任何一种类型中使用a.但是,app想要runDB在两种不同类型(Person和Food)中使用参数的主体,所以我们需要传递一个可以适用于正文中任意数量的不同类型的参数.因此app需要这种类型
app :: MonadIO m => (forall a. SqlPersistT IO a -> m a) -> ScottyM ()
Run Code Online (Sandbox Code Playgroud)
(我建议将MonadIO约束保持在外面,forall但你也可以把它放在里面.)
编辑:
幕后发生的事情如下:
(F a -> G a) -> X意思是forall a. (F a -> G a) -> X,意思是/\a -> (F a -> G a) -> X. /\是类型级别的lambda.也就是说,调用者可以传入单个类型a和类型的函数来F a -> G a进行特定选择a.
(forall a. F a -> G a) -> X意味着(/\a -> F a -> G a) -> X调用者必须传入一个函数,被调用者可以专注于许多选择a.
让我们玩游戏:
Prelude> let f str = (read str, read str)
Prelude> f "1" :: (Int, Float)
(1,1.0)
Run Code Online (Sandbox Code Playgroud)
按预期工作.
Prelude> let f str = (read1 str, read1 str) where read1 = read
Prelude> f "1" :: (Int, Float)
(1,1.0)
Run Code Online (Sandbox Code Playgroud)
也工作.
Prelude> let f read1 str = (read1 str, read1 str)
Prelude> f read "1" :: (Int, Float)
<interactive>:21:1:
Couldn't match type ‘Int’ with ‘Float’
Expected type: (Int, Float)
Actual type: (Int, Int)
In the expression: f read "1" :: (Int, Float)
In an equation for ‘it’: it = f read "1" :: (Int, Float)
Run Code Online (Sandbox Code Playgroud)
但事实并非如此.有什么区别?
最后一个f有下一个类型:
Prelude> :t f
f :: (t1 -> t) -> t1 -> (t, t)
Run Code Online (Sandbox Code Playgroud)
所以它没有明确的原因,元组的两个元素应该具有相同的类型.
修复是这样的:
Prelude> :set -XRankNTypes
Prelude> let f read1 str = (read1 str, read1 str); f :: (Read a1, Read a2) => (forall a . Read a => str -> a) -> str -> (a1, a2)
Prelude> f read "1" :: (Int, Float)
(1,1.0)
Run Code Online (Sandbox Code Playgroud)
不太可能,我可以得到很好的解释RankNTypes,所以我甚至没有尝试.网络上有足够的资源.
要真正回答明显继续使你神秘化的标题问题:当你不提供显式签名时,Haskell总是为函数选择最通用的rank-1类型.因此,app在表达式中app (runDB' pool),GHC将尝试使用类型
app :: DBRunner ActionM a -> ScottyM ()
Run Code Online (Sandbox Code Playgroud)
这实际上是简写
app :: forall a. ( DBRunner ActionM a -> ScottyM () )
Run Code Online (Sandbox Code Playgroud)
这是rank-1多态,因为所有类型变量都是在签名之外引入的(签名本身没有进行量化;参数DBRunner ActionM a实际上是单态的,因为a在那一点上是固定的).实际上,它是最通用的类型:它可以使用多态参数(runDB' pool),但也可以使用单态参数.
但事实证明,实现app不能提供这种通用性:它需要多态操作,否则它不能a为该操作提供两种不同类型的值.因此,您需要手动请求更具体的类型
app :: (forall a. DBRunner ActionM a) -> ScottyM ()
Run Code Online (Sandbox Code Playgroud)
这是rank-2,因为它有一个包含rank-1多态参数的签名.GHC无法真正知道这是你想要的类型 - 对于表达式没有明确定义的"最通用的可能的rank-n类型",因为你总是可以推进额外的量词.因此,您必须手动指定rank-2类型.