Haskell 为什么可以';GHC类型检查此函数是否涉及多态性和存在类型?
我有一些Haskell代码无法编译(使用GHC 8.0.2)。我想我理解这个基本问题,但我想更好地理解它,这样我以后就可以避免这个问题了 我的库与此类似:Haskell 为什么可以';GHC类型检查此函数是否涉及多态性和存在类型?,haskell,types,Haskell,Types,我有一些Haskell代码无法编译(使用GHC 8.0.2)。我想我理解这个基本问题,但我想更好地理解它,这样我以后就可以避免这个问题了 我的库与此类似: {-# language TypeFamilyDependencies #-} {-# language GADTs #-} {-# language RankNTypes #-} module Lib where type Key = Int class Handle m where type Connection m = c
{-# language TypeFamilyDependencies #-}
{-# language GADTs #-}
{-# language RankNTypes #-}
module Lib where
type Key = Int
class Handle m where
type Connection m = c | c -> m
withConnection :: Connection m -> m a -> IO a
class (Handle m) => Data m where
getKeyVal :: Key -> m String
data SomeConn where
SomeConn :: (Data m) => Connection m -> SomeConn
useConnection :: SomeConn -> (forall m. Data m => m String) -> IO String
useConnection (SomeConn c) action = withConnection c action
其思想是,datam
表示一类类似于ReaderT(connectionm)IO
的monad。我希望用这个typeclass的方法编写泛型函数,并通过使用SomeConn
(在运行时选择)包装的连接类型来确定确切的方法实例
现在输入以下代码
getKeyValWith :: SomeConn -> Key -> IO String
getKeyValWith c = (useConnection c). getKeyVal
给出了GHC 8.0.2中的以下错误:
• Couldn't match type ‘m0 String’
with ‘forall (m :: * -> *). Data m => m String’
Expected type: m0 String -> IO String
Actual type: (forall (m :: * -> *). Data m => m String)
-> IO String
• In the first argument of ‘(.)’, namely ‘useConnection c’
In the expression: useConnection c . getKeyVal
In an equation for ‘getKeyValWith’:
getKeyValWith c = useConnection c . getKeyVal
奇怪的是,以下方法效果很好:
getKeyValWith c k = useConnection c (getKeyVal k)
不那么令人惊讶的是,这一点也是如此:
getKeyValWith (SomeConn c) = withConnection c . getKeyVal
是否有一个简单的规则来理解为什么GHC不喜欢第一个示例,但其他示例可以?当GHC试图编译第一个定义时,有没有办法向GHC询问更多关于它在做什么的信息?我理解这可能不是惯用的Haskell(有人称之为“存在/类型类反模式”)
编辑:
我应该补充的是,即使在第一个示例中显式添加类型
getKeyVal::Key->(Data m=>m String)
,我也会遇到同样的问题。我甚至可以用我选择的类型签名(类型检查)给这个函数起自己的名字,但是我得到了同样的错误。但我现在看到,即使我显式地添加类型,在GHCI中运行:t
(带有-XRankNTypes
),它也会返回浮动到左侧的原始类型。所以我想我理解GHC为什么对我犹豫不决。我可以强制GHC使用我选择的类型吗?这是关于
的。它无法在函数之间传递多态参数,因此f。如果f
为秩2多态性,则g
不起作用。请注意以下工作:
(~.) :: ((∀ m. Data m => m String) -> z) -> (x -> (∀ m. Data m => m String))
-> x -> z
(~.) f g x = f (g x)
getKeyValWith :: SomeConn -> Key -> IO String
getKeyValWith c = useConnection c ~. getKeyVal
理想情况下,
的类型如下
(.) :: ∀ c . ((∀ y . c y => y) -> z) -> x -> ((∀ y . c y => y) -> x)
-> x -> z
从而涵盖所有特殊情况,如上述~。
。但这是不可能的——在传统情况下,cy=y~y需要推断出在任何给定情况下拾取的最薄弱的约束条件c
₀代码>–我很确定这在一般情况下是不可计算的
(一个有趣的问题是,如果编译器在类型检查之前尽可能多地内联
,就像现在使用$
所做的那样,那么我们可以走多远。如果它进行了自动eta扩展,当然可以让useConnection c.getKeyVal
工作,但是自动eta扩展通常不是一个好的idea、 ……)
通过将多态性参数包装在GADT中隐藏秩2多态性,就像您在SomeConn
中所做的那样,这是常见的解决方法。我已经读过,但内容似乎被($)的讨论所掩盖
。我正在试图了解问题的一般情况,以及如何帮助GHC理解我的代码,或者让GHC帮助我理解问题。(.getKeyVal)
的类型是Data m=>(m String->c)->Key->c
;useConnection c
的类型是(对于所有m.Data m=>m String)->IO字符串
。您知道为什么类型为m String->IO字符串
和(对于所有m.Data m=>m String)->IO字符串
无法统一?后两种定义都不需要进行统一。可能会有帮助:这可能是@user2407038的近似副本,我确实看到了这一点,我编辑了我的问题,问了一个新问题:我能强迫GHC在getKeyVal
上接受不同类型的签名吗?鉴于对我问题的编辑,在我看来,GHC根本不允许我说getKeyVal::Key->(数据m=>m字符串)
(或者更确切地说,它会说,但GHCI拒绝给我一个带有:t
的秩2类型)。你会说我的观察只是一个转移视线的问题吗?现在看来,GHC在特定上下文中出现之前,它的确切类型是getKeyVal
。你总是可以从函数的结果中移出一个通用量词,这就是为什么查询GHCi以获得:t\:Key->(∀ m.数据m=>m字符串)
给出∀ m.Data m=>Key->m String
。但是如果类型变量没有出现在参数中,这也可以被反转,GHC实际上会很乐意这样做,所以没有问题。