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

时间:2019-06-07 02:49:35

标签: haskell types language-extension

说我像这样定义一个多参数类型类:

{-# 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))

但这在名义上似乎很不方便,我也只是想知道为什么我首先得到此错误消息。

1 个答案:

答案 0 :(得分:5)

问题在于,由于decrement仅需要使用a类型,因此即使在计算机上,bc都应该找出哪种类型。函数被调用的位置(因此将多态性解析为特定类型)-因此,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-使bc依赖于a

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

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