Difference between define, let and set!

Andrea picture Andrea · Mar 23, 2011 · Viewed 21.2k times · Source

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?

Answer

John Clements picture John Clements · Mar 24, 2011

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.