[英]How to define the fibonacci sequence using a fold for natural numbers?
I am currently learning folds in the sense of structural recursion/catamorphisms.我目前正在学习结构递归/变形的意义上的折叠。 I implemented power and factorial using a fold for natural numbers.
我使用自然数的折叠实现了幂和阶乘。 Please note that I barely know Haskell, so the code is probably awkward:
请注意,我几乎不了解 Haskell,所以代码可能很笨拙:
foldNat zero succ = go
where
go n = if (n <= 0) then zero else succ (go (n - 1))
pow n = foldNat 1 (n*)
fact n = foldNat 1 (n*) n
Next I wanted to adapt the fibonacci sequence:接下来我想调整斐波那契数列:
fib n = go n (0,1)
where
go !n (!a, !b) | n==0 = a
| otherwise = go (n-1) (b, a+b)
With fib
I have a pair as second argument whose fields are swapped at each recursive call.使用
fib
我有一对作为第二个参数,其字段在每次递归调用时交换。 I am stuck at this point, because I don't understand the mechanics of the conversion process.我被困在这一点上,因为我不了解转换过程的机制。
[EDIT] [编辑]
As noted in the comments my fact
function is wrong.正如评论中指出的,我的
fact
函数是错误的。 Here is a new implementation based on a paramorphism (hopefully):这是一个基于paramorphism的新实现(希望如此):
paraNat zero succ = go
where
go n = if (n <= 0) then zero else succ (go (n - 1), n)
fact = paraNat 1 (\(r, n) -> n * r)
Let the types guide you.让类型指导您。 Here is your
foldNat
, but with a type signature:这是您的
foldNat
,但带有类型签名:
import Numeric.Natural
foldNat :: b -> (b -> b) -> Natural -> b
foldNat zero succ = go
where
go n = if (n <= 0) then zero else succ (go (n - 1))
Having another look at the go
helper in your implementation of fib
, we can note the recursive case takes and returns a (Natural, Natural)
pair.再看一下
fib
实现中的go
helper,我们可以注意到递归 case 需要并返回一个(Natural, Natural)
对。 Comparing that with the successor argument to foldNat
suggests we want b
to be (Natural, Natural)
.将其与
foldNat
的后继参数进行foldNat
表明我们希望b
是(Natural, Natural)
。 That is a nice hint on how the pieces of go
should fit:这是关于
go
各个部分应该如何适应的一个很好的提示:
fibAux = foldNat (0, 1) (\(a, b) -> (b, a + b))
(I am ignoring the matter of strictness for now, but I will get back to that.) (我暂时忽略了严格的问题,但我会回到那个问题。)
This is not quite fib
yet, as can be seen by looking at the result type.这还不是很
fib
,通过查看结果类型可以看出。 Fixing that, though, is no problem, as Robin Zigmond notes:不过,正如 Robin Zigmond 指出的那样,解决这个问题是没有问题的:
fib :: Natural -> Natural
fib = fst . foldNat (0, 1) (\(a, b) -> (b, a + b))
At this point, you might want to work backwards and substitute the definition of foldNat
to picture how this corresponds to an explicitly recursive solution.在这一点上,您可能想要反向工作并替换
foldNat
的定义来描绘这如何对应于显式递归解决方案。
While this is a perfectly good implementation of fib
, there is one major difference between it and the one you had written: this one is a lazy right fold (as is the norm for Haskell catamorphisms), while yours was clearly meant as a strict left fold.虽然这是
fib
的完美实现,但它与您编写的实现之间有一个主要区别:这是一个懒惰的右折叠(Haskell catamorphisms 的规范),而您的显然是严格的左折叠折叠。 (And yes, it does make sense to use a strict left fold here: in general, if what you are doing looks like arithmetic, you ideally want strict left, while if it looks like building a data structure, you want lazy right). (是的,在这里使用严格的左折叠确实有意义:通常,如果您正在做的事情看起来像算术,那么理想情况下您需要严格的左折叠,而如果它看起来像构建数据结构,则您需要右懒)。 The good news, though, is that we can use catamorphisms to define pretty much anything that consumes a value recursively... including strict left folds!
不过,好消息是我们可以使用 catamorphisms 来定义几乎任何递归消耗值的东西......包括严格的左折叠! Here I will use an adapted version of the foldl-from-foldr trick (see this question for a detailed explanation of that in the case of lists), which relies on a function like this:
在这里,我将使用 foldl-from-foldr 技巧的改编版本(有关列表的详细说明,请参阅此问题),它依赖于这样的函数:
lise :: (b -> b) -> ((b -> b) -> (b -> b))
lise suc = \g -> \n -> g (suc n)
The idea is that we take advantage of function composition ( \\n -> g (suc n)
is the same as g . suc
) to do things in the opposite order -- it is as if we swapped succ
and go
in the right hand side of your definition of go
.这个想法是我们利用函数组合(
\\n -> g (suc n)
与g . suc
相同)以相反的顺序做事——就好像我们交换了succ
并go
了右手你对go
的定义的一面。 lise suc
can be used as the successor argument to foldNat
. lise suc
可以用作后继参数foldNat
。 That means we will get a b -> b
function in the end rather than a b
, but that is not a problem because we can apply it to the zero value ourselves.这意味着我们最终会得到一个
b -> b
函数而不是 a b
,但这不是问题,因为我们可以自己将它应用于零值。
Since we want a strict left fold, we have to sneak in a ($!)
to make sure suc n
is eagerly evaluated:因为我们想要一个严格的左折叠,我们必须偷偷加入一个
($!)
以确保suc n
被急切地评估:
lise' :: (b -> b) -> ((b -> b) -> (b -> b))
lise' suc = \g -> \n -> g $! suc n
Now we can define a strict left fold (it is to foldNat
what foldl'
from Data.List
is to foldr
):现在我们可以定义一个严格的左折叠(它是
foldNat
什么foldl'
从Data.List
到foldr
):
foldNatL' :: b -> (b -> b) -> Natural -> b
foldNatL' zero suc n = foldNat id (lise' suc) n zero
There is a final, important detail to deal with: making the fold strict is of little use if we are lazily building a pair along the way, as the pair components will remain being built lazily.最后还有一个重要的细节需要处理:如果我们在此过程中懒惰地构建对,那么使 fold 严格没有什么用处,因为对组件将保持懒惰地构建。 We could deal with that by using
($!)
along with (,)
for building the pair in the successor function.我们可以通过使用
($!)
和(,)
在后继函数中构建对来解决这个问题。 However, I believe it is nicer to use a strict pair type instead so that we don't have to worry with that:但是,我相信使用严格对类型会更好,这样我们就不必担心:
data SP a b = SP !a !b
deriving (Eq, Ord, Show)
fstSP :: SP a b -> a
fstSP (SP a _) = a
sndSP :: SP a b -> b
sndSP (SP _ b) = b
The !
!
mark the fields as strict (note that you don't need to enable BangPatterns
to use them).将字段标记为严格(请注意,您不需要启用
BangPatterns
来使用它们)。
With everything in place, we can at last have fib
as a strict left fold:一切就绪后,我们终于可以将
fib
作为严格的左折叠:
fib' :: Natural -> Natural
fib' = fstSP . foldNatL' (SP 0 1) (\(SP a b) -> SP b (a + b))
PS: As amalloy notes, your fac
calculates n^n rather than n! PS:正如 amalloy 所指出的,您的
fac
计算的是n^n而不是n! . . That is probably a matter better left for a separate question;
这可能最好留给一个单独的问题; in any case, the gist of it is that factorial is more naturally expressed as a paramorphism on naturals, rather than as a plain catamorphism.
在任何情况下,其要点是阶乘更自然地表达为自然数上的拟态,而不是简单的 catamorphism。 (For more on that, see, for instance, the Practical Recursion Schemes blog post by Jared Tobin, more specifically the section about paramorphisms.)
(例如,有关更多信息,请参阅 Jared Tobin 的Practical Recursion Schemes博客文章,更具体地说是关于paramorphisms 的部分。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.