Haskell中含求和的递归条件定义实现方法求助
嘿,我完全懂你想要的那种贴近数学写法的递归求和函数——刚入坑Haskell的时候,我也在fold/scan的用法上绕了好几个弯,咱们一步步把这个问题掰明白~
先从最贴近数学定义的递归写法开始
数学里的求和∑ₐᵇ f(i),递归定义其实很清晰:
如果起始值a大于结束值b,和为0;否则等于f(a)加上从a+1到b的求和
直接把这个逻辑翻译成Haskell就行,完全不需要fold/scan,新手也能一眼看懂:
sumFromTo :: (Num a, Ord a) => (a -> a) -> a -> a -> a sumFromTo f a b | a > b = 0 -- 边界条件:空求和为0 | otherwise = f a + sumFromTo f (a + 1) b
试试调用:sumFromTo (\x -> x^2) 1 3 会返回14(1²+2²+3²),完美贴合数学逻辑。
用fold实现:更函数式的折叠思路
如果你想用fold来做,核心是先把「从a到b的所有i」生成一个序列,再把f(i)的结果累加起来。Haskell里用[a..b]就能生成这个序列,然后结合foldr或者foldl:
写法1:手动写fold的逻辑
sumFromToFold :: (Num a, Enum a, Ord a) => (a -> a) -> a -> a -> a sumFromToFold f a b = foldr (\x acc -> f x + acc) 0 [a..b]
这里foldr的第一个参数是「把当前元素f(x)加到累加器acc上」,初始值设为0——刚好对应a>b时序列为空,fold直接返回0的边界情况。
写法2:更简洁的组合式写法
其实可以把map f和foldr (+) 0组合起来,更符合函数式的风格:
sumFromToFold' :: (Num a, Enum a, Ord a) => (a -> a) -> a -> a -> a sumFromToFold' f a b = foldr (+) 0 $ map f [a..b]
map f [a..b]先把每个i转成f(i),再用foldr把所有值加起来,逻辑和上面完全一致,只是更紧凑。
用scan实现:看清楚每一步的累加过程
scan和fold类似,但它会返回每一步的累加结果,而不是只返回最终总和。如果你想观察求和的中间过程,比如从a开始每加一项的结果,就很适合用scan:
sumSteps :: (Num a, Enum a, Ord a) => (a -> a) -> a -> a -> [a] sumSteps f a b = scanl (+) 0 $ map f [a..b]
比如调用sumSteps (\x -> x) 1 3,会得到[0,1,3,6]——第一个0是初始值,后面依次是1、1+2、1+2+3的结果。如果只想要最终总和,取列表的最后一个元素就行(last $ sumSteps f a b),但如果只需要总和的话,fold会更高效。
新手容易踩的小坑
- 如果你用的是浮点数(比如Double),
[a..b]的步长是1,可能不符合你的需求,这时候可以用enumFromThenTo a (a+step) b来指定步长,但整数求和的话[a..b]完全够用。 - 别忘记类型约束:
Num保证可以做加法,Ord用来判断a和b的大小,Enum用来生成序列([a..b]需要这个约束)。
内容的提问来源于stack exchange,提问作者Raphael J.F. Berger

