使用可用的类实例解决类型歧义

dan*_*ght 3 haskell ambiguity typeclass ambiguous

给出以下代码:

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
Run Code Online (Sandbox Code Playgroud)

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

No instance for (Num a0) arising from the literal ‘22’
The type variable ‘a0’ is ambiguous
Run Code Online (Sandbox Code Playgroud)

很容易理解为什么会这样--Haskell允许数字文字属于具有实例的任何类型Num,并且在这里我们不能消除文字0x16(或22)应该是什么类型的歧义.

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

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

main = return $ f (0x16 :: Word16)
Run Code Online (Sandbox Code Playgroud)

或者定义一个基本上为您做注释的函数:

w16 x = x :: Word16
main = return $ f (w16 0x16)
Run Code Online (Sandbox Code Playgroud)

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

我理解类型类是开放的,所以只是因为你可以在上面引用的上下文中做出假设,这Word16C其中一个其他模块可能无法容纳的唯一数字实例.但我的问题是:是否有一些机制可以假设/强制执行该属性,因此可以使用fHaskell解析其数字参数的类型Word16而无需在调用站点进行显式注释?

上下文是我正在实现一个EDSL,当我知道我的参数将是Word16或其他一些非数字类型时,我宁愿不必包含手动类型提示.如果它让EDSL感觉更自然,我会对一些肮脏的类型/扩展滥用开放!虽然如果解决方案确实涉及顽皮的pragma,我肯定会欣赏在使用它们时我应该警惕的暗示.

And*_*ács 5

使用GHC 7.10的"淘气的pragma"快速解决方案:

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

class    C a where f :: a -> ()
instance C T where f _ = ()
instance {-# INCOHERENT #-} (w ~ Word16) => C w where f _ = ()
Run Code Online (Sandbox Code Playgroud)

并使用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 _ = ()
Run Code Online (Sandbox Code Playgroud)

在这里,GHC基本上选择了在尝试统一实例头部和约束之后剩余的任意最具体的实例.

你应该只使用这个

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

很多人建议不要永远使用IncoherentInstances,但我认为这可能是很有趣的DSL-S,如果我们观察了上述事项.


Mat*_*hid 5

对于其他任何想知道的人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不是标准的库类型类.

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