定义之间的区别,让和设置!
好吧,这是一个相当基本的问题:我正在关注SICPvideo,我对define
, let
和set!
之间的区别有些困惑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) define
和let
我更加困惑的区别。 我知道在理论上let
用来绑定局部范围内的variables。 不过,在我看来,这与define
相同,例如我可以取代
(define (fx) (let ((a 1)) (+ ax)))
同
(define (gx) (define a 1) (+ ax))
而f
和g
工作原理是一样的:特别是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讲座非常棒。