[英]Why can't GHC typecheck this function involving polymorphism and existential 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 | 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
我们的想法是Data m
代表一类类似于ReaderT (Connection m) IO
的monad。 我希望用这个类型类的方法编写泛型函数,并且确切的方法实例由包含在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询问有关它在尝试编译第一个定义时正在做什么的更多信息? 我知道这可能不是惯用的Haskell(有些人称之为“Existential / typeclass anti-pattern”)。
编辑:
我应该补充一点,即使我在第一个例子中明确添加类型getKeyVal :: Key -> (Data m => m String)
,我也遇到了同样的问题。 我甚至可以用我选择的类型签名(哪个类型签名)给这个函数指定自己的名字,但是我得到了同样的错误。 但我现在看到,即使我明确添加了类型,在GHCI上运行:t
(使用-XRankNTypes
) -XRankNTypes
返回原始类型,其中Data m =>
浮动到左侧。 所以我想我明白为什么GHC会跟我说话。 我可以强迫GHC使用我选择的类型吗?
这就是全部.
。 它无法在函数之间传递多态参数,因此f . g
如果f
是rank-2多态的话,则f . 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
因此涵盖所有特殊情况,如~.
以上。 但这是不可能的 - 它需要推断在任何特定情况下选择最弱的约束条件c
- 在传统情况下, cy = y~y₀
- 我很确定这一般是不可计算的。
(一个有趣的问题是,如果编译器内联的,我们可以走多远.
尽可能多的类型检查之前 ,因为它现在已经有做$
。如果它这样做自动ETA-扩大,它肯定能得到useConnection c . getKeyVal
工作,但自动eta扩展通常不是一个好主意......)
通过将多态参数包装在GADT中来隐藏Rank-2多态性,就像使用SomeConn
,这是通常的解决方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.