那么你知道如何对列表中的数字求和,
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs
如果列表中的元素不超过5个,那么即使您确实想在参数列表中求和的元素不超过5个,该函数也会返回正确的结果。让我们通过重命名该函数来明确我们的期望:
sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements [] = 0
sumUpToFiveElements (x:xs) = x + sumUpToFiveElements xs
这个函数在处理超过五个元素的列表时将无法返回正确结果,但至少名称是正确的。
我们能不能修复它?我们能否在计数的同时沿着输入列表前进并计算前5项?
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
当然,现在这还不对。我们现在进行了计数,但出于某种原因,我们忽略了计数器。如果我们想要不超过5个元素,什么是正确的时机来反应计数器?让我们尝试 counter == 5
:
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5 [] = 0
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
但是为什么当达到5时我们要求列表也为空呢?让我们不这样做:
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5 _ = 0
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
成功!当计数器达到5时,我们现在停止计数!更重要的是,我们还停止了求和!
等等,但是counter
的初始值是多少?我们没有指定它,因此对于我们函数的用户(也就是我们自己)来说很容易犯错并使用不正确的初始值。顺便问一下,正确的初始值是什么?
好的,那么让我们这样做:
sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements xs = go 1 xs
where
go counter _ | counter == 5 = 0
go counter [] = 0
go counter (x:xs) = x + go (counter + 1) xs
现在,我们没有那个多余的参数来使我们的定义变得脆弱,容易出现用户错误。
现在,听好了:
泛化! (通过使用符号值替换示例值;将5
更改为n
)。
sumUpToNElements :: Int -> [Int] -> Int
sumUpToNElements n xs = .......
........
完成。
另外一个建议:在学习 Haskell 的初期不要使用 $
,请使用明确的括号。
sumList take (x:xs) = x + take $ sumList xs
被解析为
sumList take (x:xs) = (x + take) (sumList xs)
这将两个不相关的数字相加,然后使用结果作为一个函数,并用(sumList xs)
作为参数调用该函数(换句话说,这是一个错误)。
如果您使用显式括号,您可能不会这样写。
take $ sumList xs
会做什么? - Willem Van Onsem