When this matters
Give values names with define and let. Use set! when a procedure needs to read an updated value on a later call.
The link between a name and its value is called a binding, and each binding keeps its value in one cell.
See it run
Save this complete program as bindings.lspx. Install the Native CLI first, or run each example separately in the Playground.
(define x 1)
(define get (lambda () x))
(set! x 2)
(get)lispex run bindings.lspxOutput
2The command exits with status 0 and creates no files.
Read the example
get reads x when you call it. By then, set! has changed x from 1 to 2, so (get) returns 2.
A binding connects a name to a place that stores its value. That place is called a cell. set! updates the value in an existing cell.
A name's scope is the part of the program where it can be used. Lispex uses lexical scope, so the source structure determines which binding a name refers to.
Use a local name
An inner name can hide an outer name. Run this example separately from the first one.
(define x 1)
(define get (lambda () x))
(list (let ((x 10)) (set! x 11) x) (get))Result
(11 1)The inner x has its own cell. Changing it to 11 leaves the outer x at 1. This is shadowing. get still reads the outer x, where the procedure was created.
Use an earlier local value
(let* ((x 6)
(y (+ x 1)))
(* x y))Observed result
42let* creates names from top to bottom. The expression (+ x 1) can therefore use x, which is already 6. An expression that supplies a binding's starting value is called an initializer.
Let a local procedure call itself
(letrec ((f (lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
(f 5))Observed result
120f computes the factorial of 5. letrec creates all its cells before evaluating the initializers. The procedure can therefore refer to f in its own body. Several procedures in one letrec can also call each other.
Reading a cell before it has a value raises E321. For example, (letrec ((x x)) x) reads x too early. Lispex keeps an internal marker in that cell until initialization. The marker is not a value a Lispex program can produce.
Redefine a top-level name
(define x 1)
(define get (lambda () x))
(define x 3)
(get)Result
3A top-level definition sits outside a procedure or local binding form. Repeating define there updates the existing cell. The earlier get therefore reads the new value.
Choose quickly
| Form | Use it for | Visibility of initializers |
|---|---|---|
define | a top-level name or named procedure | current top-level bindings |
let | independent local values | outer bindings |
let* | locals that depend on earlier locals | outer bindings and earlier bindings in this let* |
letrec | local procedures that call themselves or each other | all bindings in this letrec; reading an uninitialized cell raises E321 |
set! | updating an existing cell | the bindings visible where set! appears |
A common mistake
set! on an unbound name is E303. It never creates a binding.
Keep going
Read Procedures to define and call your own procedures. The closures guide shows when two counters share a value and when each keeps its own.