MultiParamTypeClasses - 为什么这个类型变量不明确?

MultiParamTypeClasses - Why is this type variable ambiguous?

假设我定义了一个multi-parameter type class:

{-# 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 的类型,这在 class 声明中表示。

我想到了以下解决方法:

data Table a b = Table (a -> b) ((Table a b) -> (Table a b))

但这在符号上似乎很不方便,我也只是想知道为什么我一开始会收到这条错误消息。

问题在于,由于decrement只需要a类型,所以无法确定bc应该是哪种类型,甚至在调用函数的地方(从而将多态性解决为特定类型)——因此,GHC 将无法决定使用哪个实例。

例如:假设您有两个 Table 实例:Table Int String Bool 和 Table Int Bool Float;您在应该将一个 Int 映射到另一个 Int 的上下文中调用您的函数 d - 问题是,它匹配 both 个实例! (a 两者都是 Int)。

请注意,如果您使函数等于 evalutate:

d = evalutate

然后编译器接受它。这是因为,由于 evalutate 取决于三个类型参数 a、b 和 c,调用站点的上下文将允许 non-ambiguous 实例解析 - 只需检查 a、b 的类型, 和 c 在它被调用的地方。

当然,对于 single-parameter 类型 类 这通常不是问题 - 只有一种类型需要解决;当我们处理多个参数时,事情就变得复杂了...

一个常见的解决方案是使用 functional dependencies - 使 bc 依赖于 a:

 class Table a b c | a -> b c where
  decrement :: a -> a
  evalutate :: a -> b -> c

这告诉编译器,对于给定类型 a 的每个 Table 实例,将有一个且只有一个实例(bc 将由 a) 唯一确定);所以它会知道不会有任何歧义并愉快地接受你的d = decrement