Names for values
Naming a value with define, local names with let, asking a value what it is, and the one way a name can change.
A name is a label, not a box
(define name value) takes a value and gives it a name you can use afterwards. The value is worked out first, then the name points at it. Nothing is copied or moved: the name is just a label you can read back later.
Names in Scheme can contain almost anything except brackets, quotes and spaces, so question marks are common and worth using — a name like empty? reads as a question and tells the next reader what kind of answer to expect.
Names for one expression
let makes names that exist only for the expression it wraps. Bind a to 2 and b to 3, multiply them, print the answer, and the names are gone. That is how a short calculation stays readable without leaving names behind.
The version with a star after the word binds its names one at a time, so a later name can use an earlier one. That is the form to reach for when the second value is computed from the first.
What a definition actually does
define evaluates the expression on the right first, then makes the name point at the result. The name is not a promise about future values; it is a label on one value, and the same value can carry several labels.
A definition inside a function body is local to that body, just like a let. The name is created when the function runs and disappears when it answers, so two calls do not share it.
Order matters at the top of a file. A name must exist before an expression reads it, so definitions usually sit above the code that uses them, even though Scheme does not require a separate declaration section.
Shadowing, and why it is useful
A local name may reuse a name from outside. The inner binding shadows the outer one for as long as it is in scope; when the expression ends, the outer value is visible again.
Shadowing is not a mistake, it is a tool: a function can call its parameter x without caring what x means at the call site. The function only promises to use the value it was handed.
set! is different. It does not make a new name; it moves an existing one. That is why set! on a name that was never defined is an error, while a let with the same spelling is perfectly fine.
Binding values to names
(define greeting "hello")
(define count 3)
(print greeting)
(print count)
; One expression, two local names.
(print (let ((a 2) (b 3))
(+ a b)))
; let* lets the second name see the first.
(print (let* ((a 2) (b (+ a 1)))
(* a b)))
; Ask a value what it is.
(print (number? count))
(print (string? greeting))Definitions happen in order
define is not a declaration the compiler reads ahead of time; it is an expression evaluated in order like everything else. When the line runs, the value on the right is worked out and the name is bound to it. That is why a name cannot be used above its definition, and why redefining a name later simply changes what the rest of the file sees.
The top level is a scope too. A name defined there is visible everywhere below, which is convenient for a small program and risky for a large one: any function can read it, and any later definition can replace it. Prefer parameters and let when a value only belongs to one calculation.
A local name is a fresh name
Every call to a function with a let inside it builds a new set of names. Two calls do not share them, even though the source spells them the same way. That is what makes recursion safe: the n in one call is a different binding from the n in the call it makes.
Shadowing follows from the same rule. An inner let can reuse an outer spelling, and the inner one wins until its body ends. The outer value is not destroyed; it is simply out of sight for that expression.
Three things to keep straight. Defining the same name twice replaces the binding quietly, so a repeated definition is easy to miss. A let body cannot see its own names while the values are being computed, which is what let* is for. And set! moves a name, not the value it used to hold, so a second name keeps the old value.
Try these next
- Rewrite the
letin the example as alet*that also computes a total with tax. - Print
(let ((x 1)) (let ((x 2)) x))and then printxoutside it. - Define a name, read it into a second name, change the first with
set!, and print both.
A name for every value
Run it and match each printed line to the name that produced it. Then add a name for a temperature in degrees, print it, and change `total` once more before you print it.
A total built in steps
Run it and compare the two totals. Then change the price to 20 and add a line that prints the discount on its own.
(set! name value) changes what a name points at, and it is the only way a name can change. Scheme programmers use it sparingly — a program built from values that never change is far easier to reason about — and you will meet it again much later, for counters and vectors.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does (define born 1815) do?
