问题
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!
.
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
(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)
(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
(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.
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
?
回答1:
Do you mean (+ 1 a)
instead of (1+ a)
? The latter is not syntactically valid.
Scope of variables defined by let
are bound to the latter, thus
(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.
回答2:
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.
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.
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 :).
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.
回答3:
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.
For example, in Chez Scheme 8.0 (which has its own define
quirks, 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.
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:
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.
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.
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.
So stick with it! SICP is a brilliant book and the video lectures are wonderful.
来源:https://stackoverflow.com/questions/5406064/difference-between-define-let-and-set