我有以下代码:
class Coll c e where
map :: (e1 -> e2) -> c e1 -> c e2
merge :: (e -> e -> e) -> e -> c e -> e
sum :: (Num e) => c e -> e
sum = merge (+) 0
到目前为止一切顺利。但后来我有:
sumMap :: (Num e2) => (e1 -> e2) -> c e1 -> e2
sumMap f c = (merge (+) 0) (map f c)
编译会出错:
无法推断(Coll c e2)从上下文中使用'merge'(Coll ce)[...]可能的修复:将(Coll c e2)添加到sumMap的类型签名的上下文中[ ...]
所以我将sumMap :: (Num e2) => (e1 -> e2) -> c e1 -> e2
替换为sumMap :: (Num e2, Coll c e2) => (e1 -> e2) -> c e1 -> e2
,但之后又出现了另一个错误:
无法推断(Coll c e0)因使用上下文中的'map'而引起的[Coll ce] [...]可能的解决方法:添加修复这些类型变量的类型签名[...] ]
我很困惑,所以我会对sumMap
的定义进行评论,并运行:t (merge (+) 0) . (map (* 2))
,这会给我[...] :: (Num c, Coll c1 c, Coll c1 e) => c1 c -> c
。忽略它如何破坏变量的名称,Coll c1 e
是奇怪的; e
甚至没有在定义中使用!,为什么它在那里!?无论如何,我运行((merge (+) 0) . (map (* 2))) [1,2,3,4]
,成功返回20
。这里发生了什么?为什么只有在我不尝试将它与名称联系起来时,此功能才起作用?
答案 0 :(得分:5)
您的问题源于您定义Col
课程的方式。特别是,类定义包括两种类型c
和 e
。这意味着您可以为不同类型的元素提供不同的实例 - 可能不是您想要的。相反,您希望为每个可能适用于任何类型元素的c
创建一个实例。
将课程编写为:
会更好class Coll c where
map :: (e1 -> e2) -> c e1 -> c e2
merge :: (e -> e -> e) -> e -> c e -> e
sum :: Num e => c e -> e
sum = merge (+) 0
现在每个单独的实例仅依赖于c
,而不是其元素的类型。
我怀疑你写了class Coll c e where
,因为你想确保c
是元素的集合。 (就像在Java中编写C<E>
一样。)但是,在Haskell中这是不必要的:像c
这样的类型变量可以代表没有其他注释的参数化类型。类型系统会发现c
通过您在map
,merge
的签名中使用它的方式来获取参数。
由于这是不必要的,class Coll c e
意味着该类基于两个不同的类型变量,这些变量甚至不必相关。这意味着,要确定要使用的实例,类型系统需要知道集合和其元素的特定类型,并且在您的特定情况下,它没有足够的信息来执行此操作。如果你只是将e
从类定义中删除,那应该不是问题。