我总是这样编写我的Scheme程序(看过它们):
(define (foo x)
(let ((a ...))
((b ...))
...))
我的一个学生写道:
(define (foo x)
(define a ...)
(define b ...)
...)
两者都给出相同的结果。我理解行为上的差异:第一个创建一个指向过程应用程序框架的新框架,而后者直接修改过程应用程序框架。后者会产生稍好的表现。
另一个区别是前者避免在过程体中的一系列指令之前使用隐式begin
。
为什么以前的标准风格?
答案 0 :(得分:5)
实际上,两种风格都很好。 In fact, some people prefer to use internal definitions.
此外,后者不一定“直接修改程序应用程序框架”;内部定义与letrec
(对于符合R5RS的系统)或letrec*
(对于符合R6RS和R7RS的系统)的处理方式相同。因此,您的第二个示例实际上与:
(define (foo x)
(letrec* ((a ...)
(b ...))
...))
事实上,为了使用示例,Racket将内部定义重写为等效的letrec*
表达式,因此性能没有差异(除let
和letrec*
之间存在差异外,当然)。
答案 1 :(得分:4)
这不完全等同。过程体中的define
更像是letrec
,因此您可能会感到惊讶的是,在完成所有过程之前,不能使用define
中绑定的值,并且过程的主体是被执行。想象一下你想做x + y * z:
(define (test x y z)
(let ((ytimesz (* y z)))
(let ((sum (+ x ytimesz)))
(dosomething sum))))
你在这里使用嵌套的原因是因为ytimesz
无法在同一个版本中访问。我们有let*
(define (test x y z)
(let* ((ytimesz (* y z)) (sum (+ x ytimesz)))
(dosomething sum)))
letrec
和letrec*
相似但允许递归,所以在lambda中你可以调用另一个绑定成员或其自身。现在,根据您使用的Scheme版本,您将在编写时获得其中一个:
(define (test x y z)
(define ytimesz (* y z))
(define answer (+ x ytimesz)) ;might work, might not
(dosomething answer))
在#!R7RS
,#!R6RS
和#!Racket
中,这完全可以,因为它被定义为letrec*
。
然而,在#!R5RS
中,它根本不起作用。重写以letrec
完成,它将所有变量(ytimesz
和answer
)初始化为未定义的值,然后在set!
之前将表达式的评估分配给临时变量 - 将变量赋值给临时值的值以确保它们中的任何一个的使用最终都是未定义的值和一些偶数信号错误(Racket在R5RS模式下执行。对于lambda表达式,其中在身体中的绑定被评估在通话时间,这是没有问题的,而且适用于这些letrec
和内部define
最初是用于。
我使用define
来存储简单的值和过程。第二个我认为我需要使用预先计算的值,我可能会将整个事物重写为let*
或合并define
和简单的let
。