使用可用的 class 个实例解决类型歧义
Resolving type ambiguities using available class instances
给定以下代码:
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)应该是。
作为人类阅读我打算做的事情也很清楚——只有一个可用的 class C
实例满足 Num
约束,所以很明显我打算使用那个,所以 0x16
应该被视为 Word16
。
我知道有两种方法可以修复它:要么用它的类型注释文字:
main = return $ f (0x16 :: Word16)
或者定义一个基本上为您做注释的函数:
w16 x = x :: Word16
main = return $ f (w16 0x16)
我已经尝试了第三种方法,将 default (Word16)
放在文件的顶部,希望 Haskell 会选择它作为数字文字的默认类型,但我想我是误解了 default
关键字应该做什么,因为它不起作用。
我知道 typeclass 是开放的,所以只是因为你可以在上面引用的上下文中做出假设 Word16
是 C
的唯一可能的数字实例不保存在其他模块中。但我的问题是:是否有某种机制可以让我 assume/enforce 得到 属性,这样就可以使用 f
并让 Haskell 解析其数字类型Word16
的参数在调用站点没有显式注释?
上下文是我正在实施 EDSL,当我知道我的参数将是 Word16
或其他一些非数字类型时,我宁愿不必包含手动类型提示。如果让 EDSL 感觉更自然,我愿意接受一些肮脏的 types/extensions 滥用!尽管如果解决方案确实涉及顽皮的编译指示,我肯定会感谢提示,提示我在使用它们时应该注意什么。
使用 GHC 7.10 "naughty pragmas" 的快速解决方案:
{-# 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 基本上选择了一个任意的最具体的实例,该实例在尝试统一实例头和约束后仍然存在。
你应该只在
时使用它
- 您有一组固定的实例并且不导出 class。
- 对于 class 方法的所有用例,有一个可能的最具体的实例(给定约束)。
许多人建议不要永远使用IncoherentInstances
,但我认为如果我们遵守上述注意事项,它对于 DSL-s 会非常有趣。
对于其他想知道 default
的人(我知道我是!)
https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-750004.3
引用第 4.3.4 节:
In situations where an ambiguous type is discovered, an ambiguous type variable, v, is defaultable if:
- v appears only in constraints of the form C v, where C is a class, and
- at least one of these classes is a numeric class, (that is, Num or a subclass of Num), and
- all of these classes are defined in the Prelude or a standard library.
这就解释了为什么您的 default
子句被完全忽略了; C
不是标准库类型-class.
(至于为什么这是规则……这帮不了你。大概是为了避免破坏任意用户定义的代码。)
给定以下代码:
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)应该是。
作为人类阅读我打算做的事情也很清楚——只有一个可用的 class C
实例满足 Num
约束,所以很明显我打算使用那个,所以 0x16
应该被视为 Word16
。
我知道有两种方法可以修复它:要么用它的类型注释文字:
main = return $ f (0x16 :: Word16)
或者定义一个基本上为您做注释的函数:
w16 x = x :: Word16
main = return $ f (w16 0x16)
我已经尝试了第三种方法,将 default (Word16)
放在文件的顶部,希望 Haskell 会选择它作为数字文字的默认类型,但我想我是误解了 default
关键字应该做什么,因为它不起作用。
我知道 typeclass 是开放的,所以只是因为你可以在上面引用的上下文中做出假设 Word16
是 C
的唯一可能的数字实例不保存在其他模块中。但我的问题是:是否有某种机制可以让我 assume/enforce 得到 属性,这样就可以使用 f
并让 Haskell 解析其数字类型Word16
的参数在调用站点没有显式注释?
上下文是我正在实施 EDSL,当我知道我的参数将是 Word16
或其他一些非数字类型时,我宁愿不必包含手动类型提示。如果让 EDSL 感觉更自然,我愿意接受一些肮脏的 types/extensions 滥用!尽管如果解决方案确实涉及顽皮的编译指示,我肯定会感谢提示,提示我在使用它们时应该注意什么。
使用 GHC 7.10 "naughty pragmas" 的快速解决方案:
{-# 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 基本上选择了一个任意的最具体的实例,该实例在尝试统一实例头和约束后仍然存在。
你应该只在
时使用它- 您有一组固定的实例并且不导出 class。
- 对于 class 方法的所有用例,有一个可能的最具体的实例(给定约束)。
许多人建议不要永远使用IncoherentInstances
,但我认为如果我们遵守上述注意事项,它对于 DSL-s 会非常有趣。
对于其他想知道 default
的人(我知道我是!)
https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-750004.3
引用第 4.3.4 节:
In situations where an ambiguous type is discovered, an ambiguous type variable, v, is defaultable if:
- v appears only in constraints of the form C v, where C is a class, and
- at least one of these classes is a numeric class, (that is, Num or a subclass of Num), and
- all of these classes are defined in the Prelude or a standard library.
这就解释了为什么您的 default
子句被完全忽略了; C
不是标准库类型-class.
(至于为什么这是规则……这帮不了你。大概是为了避免破坏任意用户定义的代码。)