简体   繁体   English

如何使用自然数的折叠来定义斐波那契数列?

[英]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相同)以相反的顺序做事——就好像我们交换了succgo了右手你对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.Listfoldr ):

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.

相关问题 如何在Haskell中将自然数范围定义为char? - How to define range of natural numbers as chars in Haskell? 使用 Haskell 中的列表理解定义无限的斐波那契数序列 - Defining an infinite sequence of Fibonacci numbers using list comprehension in Haskell 如何定义一个函数,该函数采用数据类型的自然数并返回它们的总和? - How to define a function that takes natural numbers of a data type and return their sum? 用自然数的平方,立方和阶乘生成序列 - generate sequence with squares, cubes and factorials of natural numbers 不使用 zipWith 的斐波那契数列 - Fibonacci numbers without using zipWith 使用 fix 递归地生成斐波那契数 - 这个例子是如何工作的? - Using fix to generate Fibonacci numbers recursively - how does this example work? 如何找出一个数字是否在斐波那契数列中 - How to find out if a number is in the Fibonacci Sequence 理解用于在自然数上定义幺半群的方程的问题 - Problem with understanding an equation used to define a monoid on the natural numbers BST:如何根据变形折叠定义“插入”? - BST: how to define `insert` in terms of catamorphic fold? 在 Haskell 中使用列表推导表示斐波那契数 - Representing Fibonacci numbers using a list comprehension in Haskell
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM