1

假设想要一个函数,获取一个数字列表,它返回相邻数字对的总和,除了两条边,它只返回边号。例如:

[1,2,3,4,5] --> [1,3,5,7,9,5]

我想出了以下功能:

p1 :: Num a => [a] -> [a]
p1 []         = []
p1 (x:xs)     = x:p2 (x:xs)

p2 :: Num a => [a] -> [a]
p2 [x]        = [x]
p2 (x1:x2:xs) = (x1+x2):p2 (x2:xs)

(没关系是否有更好的方法来做到这一点......)

p2只被p1. p2也永远不会用空列表调用,只有列表至少有一个元素。但是,p2它仍然是一个部分功能,它会触发一个非详尽的模式匹配警告ghc -Wall

处理这个问题的公认方法是什么?我只是不写p2 []案例吗?或者p2 [] = undefined,或者p2 [] = unused_value,或者别的什么?

4

2 回答 2

2

您可以p2使用非空列表类型进行总计。或者,内联和取消标准非空列表类型(a, [a]),您可以编写如下内容:

p2 :: Num a => a -> [a] -> [a]
p2 x []      = [x]
p2 x (x':xs) = (x+x'):p2 x' xs

您当然必须p1稍微修改以匹配:

p1 :: Num a => [a] -> [a]
p1 []         = []
p1 (x:xs)     = x:p2 x xs -- the call to p2 on this line changed
于 2015-03-24T01:43:46.797 回答
2

最常见的方法是放入最后一个案例,例如

p2 [] = error "impossible! p2 received an empty list"

它应该包含足够的信息来追踪错误消息中的问题根源。不过,这并不理想。人们(包括我)这样做是因为它是解决实际问题的实用方法。但有时代码会演变,你最终会遇到这种情况,并弄清楚为什么会出现调试混乱。

通过将边界条件更改为总函数来消除问题要好得多:

p1 :: Num a => [a] -> [a]
p1 xs = zipWith (+) xs' $ tail xs'
  where
    xs' = [0] ++ xs ++ [0]

现在您知道再多的重构都不会触发隐藏的错误案例,因为没有隐藏的错误案例。

当然,这种转变并不总是可能的。但它通常是,而且总是值得花一分钟时间寻找。您要么会找到更好的方法,要么会想出为什么该函数部分包含在注释中的原因。

于 2015-03-23T23:26:24.257 回答