【问题标题】:Resolving type ambiguities using available class instances使用可用的类实例解决类型歧义
【发布时间】:2015-04-22 02:25:27
【问题描述】:

给定以下代码:

import Data.Word

data T = T deriving (Eq, Show)

class    C a      where f :: a -> ()
instance C T      where f _ = ()
instance C Word16 where f _ = ()

main = return $ f 0x16

GHC 抱怨它无法推断文字 0x16 的类型应该是什么错误:

No instance for (Num a0) arising from the literal ‘22’
The type variable ‘a0’ is ambiguous

很容易看出为什么会这样——Haskell 允许数字文字是具有Num 实例的任何类型,在这里我们不能区分文字 0x16 的类型(或22) 应该是。

作为人类阅读我打算做的事情也很清楚——只有一个满足Num约束的C类的可用实例,所以显然我打算使用那个0x16应该被视为Word16

我知道有两种方法可以修复它:要么用它的类型注释文字:

main = return $ f (0x16 :: Word16)

或定义一个本质上为您执行该注释的函数:

w16 x = x :: Word16
main = return $ f (w16 0x16)

我尝试了第三种方法,将 default (Word16) 粘贴在文件顶部,希望 Haskell 会选择它作为数字文字的默认类型,但我想我误解了 default 关键字是什么应该这样做,因为那不起作用。

我知道类型类是开放的,所以只是因为您可以在上面引用的上下文中假设Word16C 的唯一数字实例,它可能不包含在其他模块中。但我的问题是:是否有某种机制可以让我假设/强制执行该属性,以便可以使用 f 并让 Haskell 将其数字参数的类型解析为 Word16,而无需在调用站点进行显式注释?

上下文是我正在实现 EDSL,当我知道我的参数将是 Word16 或其他一些非数字类型时,我宁愿不必包含手动类型提示。如果它使 EDSL 感觉更自然,我愿意接受一些肮脏的类型/扩展滥用!虽然如果解决方案确实涉及顽皮的编译指示,我肯定会很感激提示我在使用它们时应该注意什么。

【问题讨论】:

    标签: haskell typeclass ambiguity ambiguous


    【解决方案1】:

    使用 GHC 7.10 使用“顽皮的编译指示”的快速解决方案:

    {-# LANGUAGE TypeFamilies, FlexibleInstances #-}
    
    class    C a where f :: a -> ()
    instance C T where f _ = ()
    instance {-# INCOHERENT #-} (w ~ Word16) => C w where f _ = ()
    

    使用 GHC 7.8:

    {-# LANGUAGE TypeFamilies, FlexibleInstances, IncoherentInstances #-}
    
    class    C a where f :: a -> ()
    instance C T where f _ = ()
    instance (w ~ Word16) => C w where f _ = ()
    

    在这里,GHC 本质上是选择一个任意的最具体的实例,该实例在尝试统一实例头和约束后仍然存在。

    你应该只在以下情况下使用它

    • 您有一组固定的实例并且不导出类。
    • 对于类方法的所有用例,都有一个可能的最具体的实例(给定约束)。

    许多人建议不要永远使用IncoherentInstances,但我认为如果我们遵守上述注意事项,这对 DSL-s 来说会很有趣。

    【讨论】:

    • 正是我正在寻找的那种东西,谢谢!是的,我基本上是在滥用类型类来给我的 EDSL 中的某些指令带来某种重载——最后只有那些指令而不是它们所依赖的类型类会被导出,所以我认为我应该是安全的。谢谢!
    【解决方案2】:

    对于其他想知道default 的人(我知道我是!)

    https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-750004.3

    引用第 4.3.4 节:

    在发现不明确类型的情况下,不明确类型变量 v 是可默认的,如果:

    • v 仅出现在 C v 形式的约束中,其中 C 是一个类,并且
    • 这些类中至少有一个是数字类(即 Num 或 Num 的子类),并且
    • 所有这些类都在 Prelude 或标准库中定义。

    这就解释了为什么你的default 子句被完全忽略了; C 不是标准库类型类。

    (至于为什么这是规则......无法帮助你。大概是为了避免破坏任意用户定义的代码。)

    【讨论】:

    • ExtendedDefaultRules 扩展解除了最后一个限制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多