Haskell 中的(共同)递归定义如何工作?

How does (co)recursive definition work in Haskell?

我正在尝试使用这门语言来开始学习,但我对递归定义的工作原理感到困惑。

例如,我们取三角数的序列(TN n = sum [1..n])

提供的解决方案是:

triangularNumbers = scanl1 (+) [1..]

到目前为止,还不错。

但我想出的解决方案是:

triangularNumbers = zipWith (+) [1..] $ 0 : triangularNumbers

这也是正确的。

现在我的问题是:这如何转化为较低级别的实施?当满足这样的递归定义时,幕后到底发生了什么?

这是一个简单的递归函数,可以得到第 n 个三角数:

triag 0 = 0
triag n = n + triag (n-1)

您的解决方案 triag' = zipWith (+) [1..] $ 0 : triag' 更奇特:它是 corecursive (click, click)。不是通过将第 n 个数减少到较小的输入值来计算它,而是通过递归指定下一个值来定义整个无限三角数序列,给定一个初始段。

Haskell如何处理这种核心偏移?当它遇到您的定义时,实际上不会执行任何计算,它会被推迟到需要结果进行进一步计算时才执行。当您访问列表的特定元素时 triag',Haskell 开始根据定义计算列表的元素,直到访问的元素为止。有关更多详细信息,我发现 this 篇关于惰性求值的文章很有帮助。总而言之,惰性评估非常有用,除非您需要预测内存使用情况。

Here是一个类似的SO问题,对fibs = 0 : 1 : zipWith (+) fibs (tail fibs)的求值有一步一步的解释,斐波那契数列的核心递归定义