为什么GHC不能检查这个涉及多态性和存在类型的函数?

时间:2017-08-31 00:44:42

标签: 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 | 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(有些人称之为"存在/类型类反模式")。

修改

我应该补充一点,即使我在第一个示例中明确添加了类型getKeyVal :: Key -> (Data m => m String),我也会遇到同样的问题。我甚至可以用我选择的类型签名(哪个类型签名)给这个函数指定自己的名字,但是我得到了同样的错误。但我现在看到,即使我明确添加了类型,在GHCI上运行:t(带有-XRankNTypes)也会让我回到原始类型,其中Data m =>浮动到左侧。所以我想我明白为什么GHC会跟我说话。我可以强迫GHC使用我选择的类型吗?

1 个答案:

答案 0 :(得分:4)

这完全是.。它无法在函数之间传递多态参数,因此f . g如果f是rank-2多态的话,(~.) :: ((∀ 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等所有特殊情况。但这是不可能的 - 它需要推断在任何特定情况下挑选最弱的约束c y = y~y₀ - 在传统情况下,. - 并且我非常确定这是一般无法计算的

(一个有趣的问题是,如果编译器在类型检查之前尽可能多地内联$,我们可以获得多少,因为它现在已经用{{1}如果它进行了自动eta扩展,它肯定可以useConnection c . getKeyVal起作用,但自动eta扩展通常不是一个好主意......)

通过将多态参数包装在GADT中来隐藏Rank-2多态性,就像使用SomeConn一样,是常用的解决方法。