关于递归:Haskell – 无法将预期类型’b’与实际类型’a’匹配

Haskell - Couldn't match expected type ‘b’ with actual type ‘a’

刚开始学习哈斯克尔和我试图实现一个max函数来递归地查找列表的max

1
2
3
4
5
max' :: (Num b) => [a] -> b
max' [] = 0
max' (x:xs)
    | x > max' xs = x
    | otherwise = max' xs

但是我在尝试编译时出错了

Couldn't match expected type ‘b’ with actual type ‘a’
‘a’ is a rigid type variable bound by
the type signature for:
max' :: forall b a. (Num b, Num a) => [a] -> b
at implementingFunctions.hs:5:1-34
‘b’ is a rigid type variable bound by
the type signature for:
max' :: forall b a. (Num b, Num a) => [a] -> b
at implementingFunctions.hs:5:1-34

有人能帮我理解出什么问题吗?


max'[a]作为输入(基于签名):a的列表,但返回b。这意味着你已经写过了——不管列表中元素的类型如何——只要是Num b,我们可以选择任何类型的b作为我们想要的输出。但这没有道理。如果我们输入一个字符串列表,我们当然可以计算"最大的字符串"(按字典法),但不能将其作为Num返回。

另一个问题是,您使用(>) :: Ord a => a -> a -> Bool功能(作为保护)。但是您没有在函数签名中指定输入元素的类型必须是Ordtypeclass的实例。所以你不能比较元素。

最小修正是将输入类型限制为b

1
2
3
4
5
max' :: (Ord b, Num b) => [b] -> b
max' [] = 0
max' (x:xs)
    | x > max' xs = x
    | otherwise = max' xs

也就是说,如果我们提供一个空的列表,那么返回一个0就没有多大意义。这将导致一个奇怪的事实,即max []实际上大于max [-1]:通常我们期望超集的最大值大于或等于集合的最大值。

因此,max'函数可能最好被看作是非全函数:不是每个输入都会产生输出的函数。在这种情况下,空列表不会。

我们可以用以下方法将其重写为错误:

1
2
3
4
5
6
max' :: Ord b => [b] -> b
max' [] = error"Empty list"
max' [x] = x
max' (x:xs@(_:_))
    | x > max' xs = x
    | otherwise = max' xs

现在有三种模式:(1)空列表,(2)singlton列表,(3)至少包含两个元素的列表。

然而,编写错误并不总是处理非总计函数的好方法,因为在类型签名中看不到函数是非总计函数。另一个要做的工作是使用Maybe b作为返回类型。如果没有最大值,则为Nothing;如果有最大值,则为Just x

1
2
3
4
5
6
max' :: Ord b => [b] -> Maybe b
max' [] = Nothing
max' [x] = Just x
max' (x:xs@(_:_))
    | y <- max' xs = max x y
    | otherwise = Nothing

或更短:

1
2
3
4
max' :: Ord b => [b] -> Maybe b
max' [] = Nothing
max' [x] = Just x
max' (x:xs@(_:_)) = fmap (max x) (max' xs)

例如:

1
2
3
4
5
6
Prelude> max' []
Nothing
Prelude> max' [1,4,2,5]
Just 5
Prelude> max' [-3]
Just (-3)


函数获取某个内容的列表并返回其中一个内容。但是函数的类型签名表示它接受某个[a]的列表,并返回完全不同的b。这使编译器很困惑。它不能将声明的类型签名与实际实现(也称为"类型检查")进行协调。

要解决此问题,请使类型签名与实现匹配:

1
max' :: (Num a) => [a] -> a