【发布时间】:2017-08-31 21:26:35
【问题描述】:
我有一些无法编译的 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 的单子类。我希望用这个类型类的方法编写通用函数,并让确切的方法实例由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 使用我选择的类型吗?
【问题讨论】:
-
(. getKeyVal)的类型是Data m => (m String -> c) -> Key -> c;useConnection c的类型是(forall m. Data m => m String) -> IO String。你明白为什么m String -> IO String和(forall m . Data m => m String) -> IO String类型不能统一了吗?后两个定义都不需要执行这种统一。 -
这可能与 stackoverflow.com/questions/45964162/… 几乎重复
-
@user2407038,我确实看到了,我编辑了我的问题以提出一个新问题:我可以强制 GHC 接受
getKeyVal上的不同类型签名吗?