我有以下代码:
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(因使用上下文(Coll c e(中的"合并"而产生[...]可能的解决方法:将 (Coll c e2( 添加到 sumMap 的类型签名上下文中 [...]
所以我用sumMap :: (Num e2, Coll c e2) => (e1 -> e2) -> c e1 -> e2
替换sumMap :: (Num e2) => (e1 -> e2) -> c e1 -> e2
,但随后它给出了另一个错误:
无法推断(Coll c e0(从上下文(Coll c e(中使用"地图"引起的[...]可能的解决方法:添加一个类型签名来修复这些类型变量 [...]
我很困惑,所以我注释掉了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
.这是怎么回事?为什么这个函数只有在我不尝试将其绑定到名称时才有效?
你的问题源于你定义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
排除在类定义之外,那应该不是问题。