Kent Dybvig在Letrec和letrec*的Scheme Scheme Language中给出的两个例子是:
(letrec ([sum (lambda (x)
(if (zero? x)
0
(+ x (sum (- x 1)))))])
(sum 5))
Run Code Online (Sandbox Code Playgroud)
和
(letrec* ([sum (lambda (x)
(if (zero? x)
0
(+ x (sum (- x 1)))))]
[f (lambda () (cons n n-sum))]
[n 15]
[n-sum (sum n)])
(f))
Run Code Online (Sandbox Code Playgroud)
第一个也可以写成一个名为let:
(let sum ([x 5])
((lambda (x)
(if (zero? x)
0
(+ x (sum (- x 1))))) x))
Run Code Online (Sandbox Code Playgroud)
第二个可以写成带有内部定义的let:
(let ()
(define sum (lambda (x)
(if (zero? x)
0
(+ x (sum (- x 1))))))
(define f (lambda () (cons n n-sum)))
(define n 15)
(define n-sum (sum n))
(f))
Run Code Online (Sandbox Code Playgroud)
letrec/letrec*表单似乎没有比命名let或let内部定义表单更简洁或更清晰.
有人可以告诉我一个例子,其中letrec/letrec*确实改进了代码,或者是必要的,而不是使用内部定义命名let或let.
是的,可以使用命名重写第一个示例let,但请注意,那里不需要lambda表单:
(let sum ([x 5])
(if (zero? x)
0
(+ x (sum (- x 1)))))
Run Code Online (Sandbox Code Playgroud)
这种转换有点误导 - 如果您定义单个"循环函数"(在广义的非尾递归意义上)并立即在已知输入上使用它,那么这样做很好.但通常情况下,当你看到例如你给出的例子时,目的是显示本地函数的定义和使用,所以只有因为它是一个演示玩具的例子才可能进行这种转换.
其次,请注意,命名let通常不是原始形式 - 实际上所有Scheme实现都使用的实现策略是将该形式扩展为a letrec.因此,了解letrec您是否想要理解命名let是一个好主意.(这是一个基本特征:能够通过递归范围进行自引用.)
最后,您使用的内部定义给的例子是类似named- letS:这是一个语法糖被扩展成一个letrec(这可以是适当的letrec或letrec*有R5RS,并且是一个需要letrec*在R6RS).因此,为了理解它是如何工作的,您需要了解letrec.另请注意,使用strict的某些实现letrec也会在第二个示例中进行barf,并且抱怨sum未定义.letrec*在R6RS中采用的语义的主要论点背后是这种语法糖:许多人喜欢使用内部定义,但是有一个问题,即顶层定义允许使用以前的定义,但内部定义不太方便以不正常的方式.有了letrec*内部定义,就像最常见的那样.(更准确地说,它们的工作方式就是限制重新定义,这意味着它们实际上就像模块顶层定义.)
(另请注意,(a)Racket和Chez都扩展了内部主体,以允许混合定义和表达式,这意味着扩展非常简单.)