构建列表时,描述第一个典型元素,然后将其纳入自然递归。
“自然递归”的确切定义是什么?我之所以要问的原因是因为我正在接受Daniel Friedman的编程语言原则课程,以下代码不被认为是“自然递归”:
(define (plus x y)
(if (zero? y) x
(plus (add1 x) (sub1 y))))
但是,以下代码被视为“自然递归”:
(define (plus x y)
(if (zero? y) x
(add1 (plus x (sub1 y)))))
我更喜欢“非自然递归”代码,因为它是尾递归的。但是,这样的代码被认为是诅咒。当我问到为什么我们不应该以尾递归形式编写函数时,副教师简单地回答说:“你不要乱用自然递归。”
以“自然递归”形式编写函数有什么好处?
答案 0 :(得分:6)
"天然" (或只是"结构")递归是开始教学生递归的最佳方式。这是因为它有Joshua Taylor指出的美妙保证:它保证终止[*]。学生们有足够的时间围绕这种计划,这使得这个计划成为一个规则。可以为他们节省大量的头撞墙。
当你选择离开结构递归的领域时,你(程序员)承担了额外的责任,这是为了确保你的程序在所有输入上停止;还有一件事要考虑&证明。
在你的情况下,它有点微妙。你有两个参数,并且你在第二个上进行结构递归调用。事实上,通过这种观察(程序在结构上在参数2上是递归的),我认为你的原始程序几乎和非尾部调用程序一样合法,因为它继承了相同的验证的收敛。问丹这件事;我有兴趣听听他的发言。
[*]在这里,你必须立法规定各种其他愚蠢的东西,比如打电话给其他没有终止的功能等等。
答案 1 :(得分:5)
自然递归与你正在处理的类型的自然",递归定义有关。在这里,你正在使用自然数;因为"显然"自然数是零或者是另一个自然数的后继,当你想要建立一个自然数时,你自然会输出0
或(add1 z)
来寻找其他自然数z
。递归计算。
教师可能希望您在递归类型定义和递归处理该类型的值之间建立链接。如果您尝试处理树,则不会遇到数字问题或者列表,因为你经常以不自然的方式使用自然数字"因此,你可能会对教会数字有自然的反对意见。
你已经知道如何编写尾递归函数这一事实在这种情况下是无关紧要的:这显然不是你老师谈论尾调优化的目标,至少目前
副教练起初并不是很有帮助("搞乱自然递归"听起来像#34;不要问"),但他/她给出的详细解释你给的快照更合适。
答案 2 :(得分:1)
/// <reference path="../../typings/_custom.d.ts" />
import { Component, View } from 'angular2/angular2';
import { RouterLink } from 'angular2/router';
@Component({
selector: 'my-component',
directives: [RouterLink]
})
@View({
template: `
<a [router-link]="['/page']">test</a>
`
})
export class MyComponent { }
(define (plus x y)
(if (zero? y) x
(add1 (plus x (sub1 y)))))
必须记住,y != 0
的结果一旦知道,就必须在其上计算(plus x (sub1 y))
。因此,当y达到零时,递归最深。现在回溯阶段开始,执行add1
。可以使用add1
来观察此过程。
我做了以下的追踪:
trace
这是追踪:
(require racket/trace)
(define (add1 x) ...)
(define (sub1 x) ...)
(define (plus x y) ...)
(trace plus)
(plus 2 3)
不同之处在于其他版本没有回溯阶段。它调用自己几次,但它是迭代的,因为它记住了中间结果(作为参数传递)。因此,这个过程不会消耗额外的空间。
有时候实现尾递归过程更容易或更优雅,然后编写它的迭代等价物。但出于某些目的,您不能/不能以递归方式实现它。
PS:我有一个关于垃圾收集算法的课程。这样的算法可能不是递归的,因为可能没有剩余空间,因此没有用于递归的空间。我记得一个名为“Deutsch-Schorr-Waite”的算法,起初真的很难理解。首先,他实现了递归版本只是为了理解这个概念,之后他编写了迭代版本(因此手动必须记住他来自内存的位置),相信我的递归方式更容易但不能在实践中使用...