45
votes

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?

4
You should properly indent (+ a x) in definition of f, as it's within the scope of let.YasirA
You are right, thank youAndrea
It depends on the scheme system you use.knivil
Directly from the masters (Gerald Sussman): youtu.be/dO1aqPBJCPg?t=1080pakman
@pakman The video says that define is syntactic sugar for let. So, it still doesn't clarify why we need both define and let, or under which circumstances you would use one versus the other.Bruno Rijsman

4 Answers

16
votes

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 defined 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.

41
votes

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.

9
votes

John Clements answer is good. In some cases, you can see what the defines 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))))
5
votes

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.