好的,这是一个相当基本的问题:我关注的是SICP视频,我对define
,let
和set!
之间的差异感到有些困惑。
1)根据视频中的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
用于绑定局部范围内的变量。尽管如此,在我看来,这与define
的工作方式相同,例如我可以替换
(define (f x)
(let ((a 1))
(+ a x)))
与
(define (g x)
(define a 1)
(+ a x))
和f
以及g
的工作方式相同:特别是变量a
也在g
之外未绑定。
我能看到这个有用的唯一方法是let
可能具有整个函数定义的更短范围。在我看来,总是可以添加一个匿名函数来创建必要的范围,并立即调用它,就像在javascript中一样。那么,let
的真正优势是什么?
答案 0 :(得分:32)
你的困惑是合理的:'let'和'define'都创建了新的绑定。 “让”的一个优点是它的含义非常明确;各种Scheme系统(包括Racket)之间绝对没有关于普通的'let'意味着什么的分歧。
'定义'形式是一个不同的鱼。与'let'不同,它不会用括号围绕主体(绑定有效的区域)。此外,它可能意味着顶层和内部的不同事物。不同的Scheme系统对'define'有着截然不同的含义。实际上,Racket最近通过添加可以发生的新上下文来改变'define'的含义。
另一方面,人们喜欢'定义';它具有较少的缩进,并且它通常具有“do-what-what-mean-mean”级别的范围,允许递归和相互递归过程的自然定义。事实上,前几天我被这种情况所困扰:)。
最后,'设置!';比如'let','set!'非常简单:它改变了现有的绑定。
FWIW,了解DrRacket中这些范围的一种方法(如果您正在使用它)是使用“检查语法”按钮,然后将鼠标悬停在各种标识符上以查看它们的绑定位置。
答案 1 :(得分:16)
您的意思是(+ 1 a)
而不是(1+ a)
吗?后者在语法上没有效力。
let
定义的变量范围与后者绑定,因此
(define (f x)
(let ((a 1))
(+ a x)))
在语法上是可行的,而
(define (f x)
(let ((a 1)))
(+ a x))
不是。
所有变量必须在函数的开头define
d,因此可以使用以下代码:
(define (g x)
(define a 1)
(+ a x))
虽然此代码会生成错误:
(define (g x)
(define a 1)
(display (+ a x))
(define b 2)
(+ a x))
因为定义之后的第一个表达式意味着没有其他定义。
set!
未定义变量,而是用于为变量分配新值。因此,这些定义毫无意义:
(define (f x)
(set! ((a 1))
(+ a x)))
(define (g x)
(set! a 1)
(+ a x))
set!
的有效用途如下:
(define x 12)
> (set! x (add1 x))
> x
13
虽然不鼓励,因为Scheme是一种功能语言。
答案 2 :(得分:7)
John Clements的回答很好。在某些情况下,您可以看到每个版本的Scheme中define
成为什么,这可能有助于您了解正在发生的事情。
例如,在 Chez Scheme 8.0 (它有自己的define
怪癖,特别是W6 R6RS!):
> (expand '(define (g x)
(define a 1)
(+ a x)))
(begin
(set! g (lambda (x) (letrec* ([a 1]) (#2%+ a x))))
(#2%void))
您会看到“顶级”定义变为set!
(尽管在某些情况下仅展开define
会改变一些事情!),但内部定义(即{{1}在另一个块内)成为define
。不同的方案会将这种表达扩展到不同的东西。
MzScheme v4.2.4 :
letrec*
答案 3 :(得分:2)
您可以多次使用define
,但事实并非如此
惯用语:define
表示您要为其添加定义
环境和set!
意味着你正在改变一些变量。
我不确定Guile以及为什么它会允许(set! a (+1 a))
但是
如果a
尚未定义但不起作用。通常人们会使用
define
引入一个新变量并仅使用set!
进行变异
后面。
您可以使用匿名函数应用程序而不是let
事实上,这通常正是let
扩展到的,几乎就是这样
总是一个宏。这些是等价的:
(let ((a 1) (b 2))
(+ a b))
((lambda (a b)
(+ a b))
1 2)
您使用let
的原因是它更清楚:变量名称就在值的旁边。
在内部定义的情况下,我不确定Yasir是什么 正确。至少在我的机器上,在R5RS模式下运行Racket 常规模式允许内部定义出现在中间 功能定义,但我不确定标准是什么。在任何 案例,很久以后在SICP中,内部定义的诡计就是 深入讨论。在第4章中,如何实现相互递归 探讨了内部定义及其对实现的意义 元认知翻译。
坚持下去! SICP是一本精彩的书,视频讲座很精彩。