Ok, this is a fairly basic question: I am following the SICP videos, and I am a bit confused about the differences between define
, let
and set!
.
好了,这是一个相当基本的问题:我是在跟随SICP的视频,我对定义、let和set的区别有点困惑。
1) According to Sussman in the video, define
is allowed to attach a value to avariable only once (except when in the REPL), in particular two defines in line are not allowed. Yet Guile happily runs this code
1)根据视频中的Sussman,定义允许仅将值附加到avariable(除非在REPL中),特别是两个定义在行中是不允许的。然而,Guile愉快地运行着这段代码。
(define a 1)
(define a 2)
(write a)
and outputs 2, as expected. Things are a little bit more complicated because if I try to do this (EDIT: after the above definitions)
以及输出2,如预期的那样。事情有点复杂,因为如果我尝试这样做(编辑:在上面的定义之后)
(define a (1+ a))
I get an error, while
我得到一个错误。
(set! a (1+ a))
is allowed. Still I don't think that this the only difference between set!
and define
: what is that I am missing?
是被允许的。我还是不认为这是唯一的区别!定义:我错过了什么?
2) The difference between define
and let
puzzles me even more. I know in theory let
is used to bind variables in local scope. Still, it seems to me that this works the same with define
, for instance I can replace
2)定义和让我更困惑的区别。我知道在理论上我们可以用它来绑定局部变量。不过,在我看来,这和定义是一样的,比如我可以替换。
(define (f x)
(let ((a 1))
(+ a x)))
with
与
(define (g x)
(define a 1)
(+ a x))
and f
and g
work the same: in particular the variable a
is unbound outside g
as well.
f和g都是一样的,特别是变量a也在g之外。
The only way I can see this useful is that let
may have a shorter scope that the whole function definition. Still it seems to me that one can always add an anonymous function to create the necessary scope, and invoke it right away, much like one does in javascript. So, what is the real advantage of let
?
我能看到这个有用的唯一方法是,让我们有一个更短的范围,整个函数定义。但在我看来,一个人总是可以添加一个匿名函数来创建必要的范围,并立即调用它,就像javascript中做的那样。那么,let的真正优点是什么呢?
4 个解决方案
#1
14
Do you mean (+ 1 a)
instead of (1+ a)
? The latter is not syntactically valid.
你的意思是(+ 1 a)而不是(1+ a) ?后者在语法上不是有效的。
Scope of variables defined by let
are bound to the latter, thus
由let定义的变量的范围与后者绑定在一起。
(define (f x)
(let ((a 1))
(+ a x)))
is syntactically possible, while
语法是可能的,虽然
(define (f x)
(let ((a 1)))
(+ a x))
is not.
不是。
All variables have to be define
d in the beginning of the function, thus the following code is possible:
所有的变量都必须在函数的开头定义,因此下面的代码是可能的:
(define (g x)
(define a 1)
(+ a x))
while this code will generate an error:
虽然这段代码会产生一个错误:
(define (g x)
(define a 1)
(display (+ a x))
(define b 2)
(+ a x))
because the first expression after the definition implies that there are no other definitions.
因为定义后的第一个表达式意味着没有其他定义。
set!
doesn't define the variable, rather it is used to assign the variable a new value. Therefore these definitions are meaningless:
设置!不定义变量,而是将变量赋值为新值。因此,这些定义是没有意义的:
(define (f x)
(set! ((a 1))
(+ a x)))
(define (g x)
(set! a 1)
(+ a x))
Valid use for set!
is as follows:
有效使用设置!如下:
(define x 12)
> (set! x (add1 x))
> x
13
Though it's discouraged, as Scheme is a functional language.
虽然这是不鼓励的,但Scheme是一种函数式语言。
#2
28
Your confusion is reasonable: 'let' and 'define' both create new bindings. One advantage to 'let' is that its meaning is extraordinarily well-defined; there's absolutely no disagreement between various Scheme systems (incl. Racket) about what plain-old 'let' means.
您的混淆是合理的:“让”和“定义”都创建新的绑定。“let”的一个优点是它的意义是非常明确的;不同的Scheme系统(incl.球拍)对普通的“let”的意思完全没有异议。
The 'define' form is a different kettle of fish. Unlike 'let', it doesn't surround the body (region where the binding is valid) with parentheses. Also, it can mean different things at the top level and internally. Different Scheme systems have dramatically different meanings for 'define'. In fact, Racket has recently changed the meaning of 'define' by adding new contexts in which it can occur.
“定义”形式是另一回事。与“let”不同的是,它不会用括号包围主体(绑定有效的区域)。此外,它还可以在顶层和内部表示不同的东西。不同的方案系统对“define”有截然不同的含义。事实上,球拍最近改变了“define”的含义,增加了新的语境。
On the other hand, people like 'define'; it has less indentation, and it usually has a "do-what-I-mean" level of scoping allowing natural definitions of recursive and mutually recursive procedures. In fact, I got bitten by this just the other day :).
另一方面,人们喜欢‘define’;它有更少的缩进,而且它通常有一个“do- i -mean”级别的范围,允许自然定义递归和相互递归的过程。事实上,有一天我被这事咬了。
Finally, 'set!'; like 'let', 'set!' is pretty straightforward: it mutates an existing binding.
最后,“集!”;像“我们”,“设置!非常简单:它会改变现有的绑定。
FWIW, one way to understand these scopes in DrRacket (if you're using it) is to use the "Check Syntax" button, and then hover over various identifiers to see where they're bound.
FWIW,在dr(如果您使用它)中了解这些范围的一种方法是使用“检查语法”按钮,然后将鼠标悬停在各种标识符上,以查看它们的绑定位置。
#3
5
John Clements answer is good. In some cases, you can see what the define
s become in each version of Scheme, which might help you understand what's going on.
约翰克莱门茨的回答很好。在某些情况下,您可以看到在每个版本的Scheme中定义了什么,这可能会帮助您理解正在发生的事情。
For example, in Chez Scheme 8.0 (which has its own define
quirks, esp. wrt R6RS!):
例如,在Chez Scheme 8.0(它有自己的定义怪癖,esp. wrt R6RS!):
> (expand '(define (g x)
(define a 1)
(+ a x)))
(begin
(set! g (lambda (x) (letrec* ([a 1]) (#2%+ a x))))
(#2%void))
You see that the "top-level" define becomes a set!
(although just expanding define
in some cases will change things!), but the internal define (that is, a define
inside another block) becomes a letrec*
. Different Schemes will expand that expression into different things.
您可以看到“*”定义变成了一个集合!(尽管在某些情况下扩展定义会改变事情!),但是内部定义(即在另一个块中定义)变成了letrec*。不同的方案会把这个表达扩展到不同的东西。
MzScheme v4.2.4:
MzScheme v4.2.4:创建
> (expand '(define (g x)
(define a 1)
(+ a x)))
(define-values
(g)
(lambda (x)
(letrec-values (((a) '1)) (#%app + a x))))
#4
2
You may be able to use define
more than once but it's not idiomatic: define
implies that you are adding a definition to the environment and set!
implies you are mutating some variable.
您可以使用定义不止一次,但它不是惯用的:定义意味着您要向环境和集合添加定义!这意味着你在改变某个变量。
I'm not sure about Guile and why it would allow (set! a (+1 a))
but if a
isn't defined yet that shouldn't work. Usually one would use define
to introduce a new variable and only mutate it with set!
later.
我不确定Guile和它为什么会允许(set!)a (+1 a))但是如果a没有定义,那就不应该工作。通常会使用define来引入一个新变量,并且只使用set来改变它!以后。
You can use an anonymous function application instead of let
, in fact that's usually exactly what let
expands into, it's almost always a macro. These are equivalent:
你可以使用一个匿名函数应用程序,而不是让它,事实上,这通常是让它扩展到的,它几乎总是一个宏。这些是等价的:
(let ((a 1) (b 2))
(+ a b))
((lambda (a b)
(+ a b))
1 2)
The reason you'd use let
is that it's clearer: the variable names are right next to the values.
使用let的原因是它更清楚:变量名与值相邻。
In the case of internal defines, I'm not sure that Yasir is correct. At least on my machine, running Racket in R5RS-mode and in regular mode allowed internal defines to appear in the middle of the function definition, but I'm not sure what the standard says. In any case, much later in SICP, the trickiness that internal defines pose is discussed in depth. In Chapter 4, how to implement mutually recursive internal defines is explored and what it means for the implementation of the metacircular interpreter.
在内部定义的情况下,我不确定Yasir是否正确。至少在我的机器上,在r5rs模式下运行球拍,在正常模式下允许内部定义出现在函数定义的中间,但是我不确定标准说的是什么。无论如何,在后来的SICP中,内部定义的欺骗是深入讨论的。第四章探讨了如何实现相互递归的内部定义,以及如何实现元类解释器的实现。
So stick with it! SICP is a brilliant book and the video lectures are wonderful.
所以坚持下去!SICP是一本精彩的书,视频讲座很棒。
#1
14
Do you mean (+ 1 a)
instead of (1+ a)
? The latter is not syntactically valid.
你的意思是(+ 1 a)而不是(1+ a) ?后者在语法上不是有效的。
Scope of variables defined by let
are bound to the latter, thus
由let定义的变量的范围与后者绑定在一起。
(define (f x)
(let ((a 1))
(+ a x)))
is syntactically possible, while
语法是可能的,虽然
(define (f x)
(let ((a 1)))
(+ a x))
is not.
不是。
All variables have to be define
d in the beginning of the function, thus the following code is possible:
所有的变量都必须在函数的开头定义,因此下面的代码是可能的:
(define (g x)
(define a 1)
(+ a x))
while this code will generate an error:
虽然这段代码会产生一个错误:
(define (g x)
(define a 1)
(display (+ a x))
(define b 2)
(+ a x))
because the first expression after the definition implies that there are no other definitions.
因为定义后的第一个表达式意味着没有其他定义。
set!
doesn't define the variable, rather it is used to assign the variable a new value. Therefore these definitions are meaningless:
设置!不定义变量,而是将变量赋值为新值。因此,这些定义是没有意义的:
(define (f x)
(set! ((a 1))
(+ a x)))
(define (g x)
(set! a 1)
(+ a x))
Valid use for set!
is as follows:
有效使用设置!如下:
(define x 12)
> (set! x (add1 x))
> x
13
Though it's discouraged, as Scheme is a functional language.
虽然这是不鼓励的,但Scheme是一种函数式语言。
#2
28
Your confusion is reasonable: 'let' and 'define' both create new bindings. One advantage to 'let' is that its meaning is extraordinarily well-defined; there's absolutely no disagreement between various Scheme systems (incl. Racket) about what plain-old 'let' means.
您的混淆是合理的:“让”和“定义”都创建新的绑定。“let”的一个优点是它的意义是非常明确的;不同的Scheme系统(incl.球拍)对普通的“let”的意思完全没有异议。
The 'define' form is a different kettle of fish. Unlike 'let', it doesn't surround the body (region where the binding is valid) with parentheses. Also, it can mean different things at the top level and internally. Different Scheme systems have dramatically different meanings for 'define'. In fact, Racket has recently changed the meaning of 'define' by adding new contexts in which it can occur.
“定义”形式是另一回事。与“let”不同的是,它不会用括号包围主体(绑定有效的区域)。此外,它还可以在顶层和内部表示不同的东西。不同的方案系统对“define”有截然不同的含义。事实上,球拍最近改变了“define”的含义,增加了新的语境。
On the other hand, people like 'define'; it has less indentation, and it usually has a "do-what-I-mean" level of scoping allowing natural definitions of recursive and mutually recursive procedures. In fact, I got bitten by this just the other day :).
另一方面,人们喜欢‘define’;它有更少的缩进,而且它通常有一个“do- i -mean”级别的范围,允许自然定义递归和相互递归的过程。事实上,有一天我被这事咬了。
Finally, 'set!'; like 'let', 'set!' is pretty straightforward: it mutates an existing binding.
最后,“集!”;像“我们”,“设置!非常简单:它会改变现有的绑定。
FWIW, one way to understand these scopes in DrRacket (if you're using it) is to use the "Check Syntax" button, and then hover over various identifiers to see where they're bound.
FWIW,在dr(如果您使用它)中了解这些范围的一种方法是使用“检查语法”按钮,然后将鼠标悬停在各种标识符上,以查看它们的绑定位置。
#3
5
John Clements answer is good. In some cases, you can see what the define
s become in each version of Scheme, which might help you understand what's going on.
约翰克莱门茨的回答很好。在某些情况下,您可以看到在每个版本的Scheme中定义了什么,这可能会帮助您理解正在发生的事情。
For example, in Chez Scheme 8.0 (which has its own define
quirks, esp. wrt R6RS!):
例如,在Chez Scheme 8.0(它有自己的定义怪癖,esp. wrt R6RS!):
> (expand '(define (g x)
(define a 1)
(+ a x)))
(begin
(set! g (lambda (x) (letrec* ([a 1]) (#2%+ a x))))
(#2%void))
You see that the "top-level" define becomes a set!
(although just expanding define
in some cases will change things!), but the internal define (that is, a define
inside another block) becomes a letrec*
. Different Schemes will expand that expression into different things.
您可以看到“*”定义变成了一个集合!(尽管在某些情况下扩展定义会改变事情!),但是内部定义(即在另一个块中定义)变成了letrec*。不同的方案会把这个表达扩展到不同的东西。
MzScheme v4.2.4:
MzScheme v4.2.4:创建
> (expand '(define (g x)
(define a 1)
(+ a x)))
(define-values
(g)
(lambda (x)
(letrec-values (((a) '1)) (#%app + a x))))
#4
2
You may be able to use define
more than once but it's not idiomatic: define
implies that you are adding a definition to the environment and set!
implies you are mutating some variable.
您可以使用定义不止一次,但它不是惯用的:定义意味着您要向环境和集合添加定义!这意味着你在改变某个变量。
I'm not sure about Guile and why it would allow (set! a (+1 a))
but if a
isn't defined yet that shouldn't work. Usually one would use define
to introduce a new variable and only mutate it with set!
later.
我不确定Guile和它为什么会允许(set!)a (+1 a))但是如果a没有定义,那就不应该工作。通常会使用define来引入一个新变量,并且只使用set来改变它!以后。
You can use an anonymous function application instead of let
, in fact that's usually exactly what let
expands into, it's almost always a macro. These are equivalent:
你可以使用一个匿名函数应用程序,而不是让它,事实上,这通常是让它扩展到的,它几乎总是一个宏。这些是等价的:
(let ((a 1) (b 2))
(+ a b))
((lambda (a b)
(+ a b))
1 2)
The reason you'd use let
is that it's clearer: the variable names are right next to the values.
使用let的原因是它更清楚:变量名与值相邻。
In the case of internal defines, I'm not sure that Yasir is correct. At least on my machine, running Racket in R5RS-mode and in regular mode allowed internal defines to appear in the middle of the function definition, but I'm not sure what the standard says. In any case, much later in SICP, the trickiness that internal defines pose is discussed in depth. In Chapter 4, how to implement mutually recursive internal defines is explored and what it means for the implementation of the metacircular interpreter.
在内部定义的情况下,我不确定Yasir是否正确。至少在我的机器上,在r5rs模式下运行球拍,在正常模式下允许内部定义出现在函数定义的中间,但是我不确定标准说的是什么。无论如何,在后来的SICP中,内部定义的欺骗是深入讨论的。第四章探讨了如何实现相互递归的内部定义,以及如何实现元类解释器的实现。
So stick with it! SICP is a brilliant book and the video lectures are wonderful.
所以坚持下去!SICP是一本精彩的书,视频讲座很棒。