我在尝试跟踪函数中的计数器变量时遇到了一些困难。
我创建了一个函数,它将一个数字作为单个参数,并将数字递归乘以 2,将所有数字乘以 2 相加,下面的代码更清楚地表明了我打算做什么:
sum :: Float -> Float
sum x = x + sum (2*x)
但是,我面临的困难是,我只希望函数递归十次。所以我希望一旦十个数字加在一起,它就会停止。我尝试使用计数器来跟踪函数递归的次数,但无济于事。
这是我尝试过的:
sumTen :: Float -> Float
sumTen x n
where
n=10
|n == 0 = x
|otherwise = x + sumTen (2*x) (n-1)
我意识到上面的代码不起作用,计数器n
将通过每个递归调用被赋予值十,这意味着它永远不会到达基本情况n == 0
。
使这如此困难的是必须使用一个参数调用sumTen
。在上面的代码中,我试图为函数提供一个具有预定值的默认参数n
,但它显然不起作用。
有人可以帮助我在"n"次递归调用后停止递归函数吗?
您可以使用辅助函数:
sumNRec x 0 = 0
sumNRec x times = x + sumNRec (x*2) (times - 1)
sumTen x = sumNRec x 10
让我们以一般的方式解决问题。从您的原始函数开始:
sum :: Float -> Float
sum x = x + sum (2*x)
第一步,添加一个额外的rec
参数。这个rec
代表"递归调用",是一个我们要调用的函数,而不是函数体中的所有递归调用。具体来说,只需将定义右侧出现的任何sum
替换为rec
:
sumK :: (Float -> Float) -> Float -> Float
sumK rec x = x + rec (2*x)
现在,如果我们希望递归函数执行零次,我们将得到id
。要只递归一次,我们可以使用 sumK id
因为
sumK id x = x + id (2*x) = x + 2*x
递归两次:自sumK (sumK id)
以来
sumK (sumK id) x = x + sumK id (2*x) = x + (2*x) + 2*(2*x)
依此类推:要递归n
时间,我们只需运行sumK (... (sumK id)...)
应用sumK
n
次。等价地,这可以写成作作sumK . sumK . ... . sumK $ id
。因此,要获得它,只需生成一个列表[sumK,sumK,...]
,撰写它们,并在最后应用id
。
sum10 :: Float -> Float
sum10 = compose (replicate 10 sumK) id
compose :: [a -> a] -> a -> a
compose = foldr (.) id
如果你愿意,你可以写一个小的通用助手:
recurseOnly :: Int -> ((a->a)->(a->a)) -> (a->a)
recurseOnly n funK = compose (replicate n funK) id
sum10 :: Float -> Float
sum10 = recurseOnly 10 sumK
sumTen :: Float -> Float
sumTen x = go 10 x
where
go n x
| n == 0 = x
| otherwise = x + go (n-1) (2*x)
这里有两个要点:go
是两个参数的简单递归函数,它沿着n
传递,让你在正确的时间停止。但是,由于您不想公开该参数,因此顶级sumTen
函数只是部分应用的go
版本。你甚至可以把它写成:
sumTen = go 10
where go n x = -- ...
你喜欢哪一个是一种风格选择,真的。
也许这甚至:
sumTen x = sum $ take (10+1) $ iterate (* 2) x