为啥 Haskell 中的递归习语是“'n+1' and 'n'”而不是“'n' and 'n-1'”?

Posted

技术标签:

【中文标题】为啥 Haskell 中的递归习语是“\'n+1\' and \'n\'”而不是“\'n\' and \'n-1\'”?【英文标题】:Why is the recursion idiom in Haskell "'n+1' and 'n'" and not "'n' and 'n-1'"?为什么 Haskell 中的递归习语是“'n+1' and 'n'”而不是“'n' and 'n-1'”? 【发布时间】:2011-02-16 14:26:08 【问题描述】:

我正在阅读 Graham Hutton 的 Haskell 书,在他的递归章节中,他经常在“n+1”上进行模式匹配,如下所示:

myReplicate1 0 _ = []
myReplicate1 (n+1) x = x : myReplicate1 n x

为什么是这样,而不是以下,哪个 (1) 在功能上看起来相同,(2) 在理解递归发生的事情方面更直观:

myReplicate2 0 _ = []
myReplicate2 n x = x : myReplicate2 (n-1) x

这里有什么我遗漏的吗?还是只是风格问题?

【问题讨论】:

【参考方案1】:

我认为它背后的想法是:我们可以像这样为自然数 (0, 1, ...) 定义一个类型:

data Natural = Z -- zero
             | S Natural -- one plus a number; the next number; the "successor"

0 = Z1 = S Z 等等。这个系统被称为 Peano 算术,它几乎是数学中的一个标准,作为“数字”实际上是什么的(a 的起点)定义。您可以继续将Integers 定义为Naturals 的对(-ish),依此类推。

当你这样做时,使用这样的模式匹配就变得很自然了:

myReplicate1 Z _ = []
myReplicate1 (S n) x = x : myReplicate1 n x

我认为(这只是一个猜测)n+1 模式背后的想法是我刚才描述的机器版本。所以,n+1 被认为类似于模式S n。如果你这样想,n+1 模式看起来很自然。这也清楚地说明了为什么我们有 n >= 0 的附带条件。我们只能使用Natural 类型来表示n >= 0

【讨论】:

【参考方案2】:

N+K 模式也有不同的严格含义。

例如:

f (n+1) = Just n
g n = Just (n-1)

f 对它的第一个参数是严格的,g 不是。这对于 n+k 模式没有什么特别之处,但适用于所有模式。

【讨论】:

【参考方案3】:

第一个函数中的那些是 n+k 模式(应该避免!)。两个函数做同样的事情,除了 n+k 一个不匹配负数。但是,建议使用后一个,如果你故意不想要负数,可以采用,因为 n+k 模式被分解为removed anyways。

所以不,你什么都没有错过,这确实是风格问题,但我很少在野外看到 n+k 模式。

【讨论】:

换句话说,这是风格问题,第一种风格已被弃用。 :) 它们实际上并不相同。 n+k 模式不会匹配负数。 哎呀,没想到!谢谢,sepp,我编辑了我的答案! 非常有趣 - 我想我错过了一些东西(n >= 0 位)。这是一种奇怪的魔法行为。我认为反对删除它的一个论点是“一些 Haskell 书籍使用它”很有趣 :)【参考方案4】:
myReplicate n x = take n (repeat x)

完成并完成。 :D

【讨论】:

【参考方案5】:

n+k 模式仅在 n>=0 时匹配。因此,在您的 myReplicate1 中,n+1 模式将仅匹配正数,而负数 n 将导致非穷举模式异常。在 myReplicate2 中,负数 n 将创建一个无限列表。

因此,换句话说,当您不希望模式匹配负数时,您可以使用 n+k 模式,但使用保护会更清晰。

【讨论】:

以上是关于为啥 Haskell 中的递归习语是“'n+1' and 'n'”而不是“'n' and 'n-1'”?的主要内容,如果未能解决你的问题,请参考以下文章

从 int|double 到 string 的转换的简洁 C++ 习语是啥? [复制]

Haskell中的懒惰和尾递归,为啥会崩溃?

Haskell 应用习语?

如何在 Haskell 递归调用中打印迭代?

Haskell 对字符串中的字符进行递归

需要列表的原始长度作为 Haskell 中的“变量”,但它会随着递归而不断变化 [重复]