说我像这样定义一个多参数类型类:
{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, FlexibleContexts, FlexibleInstances #-}
class Table a b c where
decrement :: a -> a
evalutate :: a -> b -> c
然后为简单起见,我想定义一个使用decrement
的函数:
d = decrement
当我尝试将其加载到ghci
(版本8.6.3)中时:
• Could not deduce (Table a b0 c0)
arising from a use of ‘decrement’
from the context: Table a b c
bound by the type signature for:
d :: forall a b c. Table a b c => a -> a
at Thing.hs:13:1-28
The type variables ‘b0’, ‘c0’ are ambiguous
Relevant bindings include d :: a -> a (bound at Thing.hs:14:1)
These potential instance exist:
instance Table (DummyTable a b) a b
这让我感到困惑,因为d
的类型恰好是decrement
的类型,它在类声明中表示。
我想到了以下解决方法:
data Table a b = Table (a -> b) ((Table a b) -> (Table a b))
但这在名义上似乎很不方便,我也只是想知道为什么我首先得到此错误消息。
答案 0 :(得分:5)
问题在于,由于decrement
仅需要使用a
类型,因此即使在计算机上,b
和c
都应该找出哪种类型。函数被调用的位置(因此将多态性解析为特定类型)-因此,GHC无法决定使用哪个实例。
例如:假设您有Table的两个实例:Table Int String Bool
和Table Int Bool Float
;您应该在应该将一个Int映射到另一个Int的上下文中调用函数d
-问题是,这两个实例都匹配! (a
均为Int。
请注意,如果您使函数等于evalutate
,请注意:
d = evalutate
然后编译器接受它。这是因为,由于evalutate
依赖于三个类型参数a,b和c,因此调用站点的上下文将允许无歧义的实例解析-只需检查a,b和b的类型是什么即可。 c在它被调用的地方。
当然,对于单参数类型类来说,这通常不是问题-只有一种类型可以解决;当我们处理多个参数时,事情变得复杂了……
一种常见的解决方案是使用functional dependencies-使b
和c
依赖于a
:
class Table a b c | a -> b c where
decrement :: a -> a
evalutate :: a -> b -> c
这告诉编译器,对于给定类型a
的Table的每个实例,将只有一个实例(b
和c
被唯一确定通过a
);因此它将知道不会有任何歧义,并愉快地接受您的d = decrement
。