繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM