定义之间的区别,让和设置!

好吧,这是一个相当基本的问题:我正在关注SICPvideo,我对defineletset!之间的区别有些困惑set!

1)根据video中的Sussman, define只允许赋值一次(除了在REPL时),特别是两行中的定义是不允许的。 然而Guile高兴地运行这个代码

 (define a 1) (define a 2) (write a) 

并按预期输出2。 事情有点复杂,因为如果我尝试这样做(编辑:在上述定义之后)

 (define a (1+ a)) 

我得到一个错误,而

 (set! a (1+ a)) 

被允许。 不过我不认为这是唯一的区别set!define :我错过了什么?

2) definelet我更加困惑的区别。 我知道在理论上let用来绑定局部范围内的variables。 不过,在我看来,这与define相同,例如我可以取代

 (define (fx) (let ((a 1)) (+ ax))) 

 (define (gx) (define a 1) (+ ax)) 

fg工作原理是一样的:特别是variablesa也是在g外面的。

我可以看到这个有用的唯一方法就是let整个函数定义的范围可以更小。 不过在我看来,总是可以添加一个匿名函数来创build必要的范围,并立即调用,就像在javascript中一样。 那么, let的真正优势是什么?

你的意思是(+ 1 a)而不是(1+ a) ? 后者在语法上不是有效的。

let定义的variables的范围必然与后者相关联,因此

 (define (fx) (let ((a 1)) (+ ax))) 

在语法上是可能的,而

 (define (fx) (let ((a 1))) (+ ax)) 

不是。

所有variables都必须在函数的开头define d,因此下面的代码是可能的:

 (define (gx) (define a 1) (+ ax)) 

而这段代码会产生一个错误:

 (define (gx) (define a 1) (display (+ ax)) (define b 2) (+ ax)) 

因为定义之后的第一个expression式意味着没有其他定义。

set! 没有定义variables,而是用来赋给variables一个新的值。 因此这些定义是没有意义的:

 (define (fx) (set! ((a 1)) (+ ax))) (define (gx) (set! a 1) (+ ax)) 

有效使用set! 如下:

 (define x 12) > (set! x (add1 x)) > x 13 

尽pipe这是一种function性语言,但它是令人沮丧的。

你的困惑是合理的:“让”和“定义”都创build新的绑定。 “让”的一个好处是它的含义非常明确; 不同的Scheme系统(包括Racket)之间绝对没有意见分歧。

“定义”forms是一个不同的水壶。 与“let”不同,它不包含括号内的正文(绑定有效的区域)。 而且,在顶层和内部可能意味着不同的事情。 不同的Scheme系统对于“define”具有明显不同的含义。 实际上,最近Racket通过添加新的上下文来改变“定义”的含义。

另一方面,人们喜欢“定义”; 它的缩进比较less,它通常有一个“do-what-I-mean”范围,允许自然地定义recursion和相互recursion的过程。 实际上,就在这一天,我被这个东西咬了一下:)。

最后,“定!”; 像'让','套! 非常简单:它改变了现有的绑定。

FWIW,在DrRacket中理解这些范围的一种方法是使用“Check Syntax”button,然后将鼠标hover在各种标识符上,查看它们绑定的位置。

约翰·克莱门斯的回答很好。 在某些情况下,您可以看到每个Scheme版本中define的内容,这可能会帮助您了解正在进行的操作。

例如,在Chez Scheme 8.0 (它有自己的define怪癖,特别是R6RS!):

 > (expand '(define (gx) (define a 1) (+ ax))) (begin (set! g (lambda (x) (letrec* ([a 1]) (#2%+ ax)))) (#2%void)) 

你看到“顶级”定义成为一个set! (尽pipe在某些情况下扩展define会改变事物!),但内部定义(即另一个块内的define )变成了letrec* 。 不同的scheme将把这个expression扩展到不同的东西。

MzScheme v4.2.4

 > (expand '(define (gx) (define a 1) (+ ax))) (define-values (g) (lambda (x) (letrec-values (((a) '1)) (#%app + ax)))) 

您可以使用多次define ,但不是惯用的: define意味着您正在为环境添加一个定义并进行set! 意味着你正在改变一些variables。

我不确定关于Guile,为什么它会允许(set! a (+1 a))但是如果a没有定义,那就不应该起作用。 通常人们会使用define来引入一个新的variables,只能用set!改变它set! 后来。

您可以使用匿名函数应用程序而不是let ,实际上这通常正是let扩展的东西,它几乎总是一个macros。 这些是等价的:

 (let ((a 1) (b 2)) (+ ab)) ((lambda (ab) (+ ab)) 1 2) 

您使用let的原因是它更清晰:variables名称紧靠值。

在内部定义的情况下,我不确定亚西尔是否正确。 至less在我的机器上,在R5RS模式下运行Racket并且在常规模式下允许内部定义出现在函数定义的中间,但是我不确定标准说的是什么。 无论如何,在SICP的很久之后,内部定义姿势的技巧将被深入讨论。 在第四章中,探讨了如何实现相互recursion的内部定义,以及实现元语言翻译的意义。

所以坚持下去! SICP是一本精彩的书,video讲座非常棒。