Change, and vectors
The two kinds of change in Scheme: repointing a name with set!, and updating a slot in a vector — plus the closure that keeps state to itself.
Repointing a name
(set! name value) changes what a name points at. It does not touch the value that was there before; it moves the name to a new one. Everything that read the name earlier keeps the value it already had, because values in Scheme are not changed behind your back.
Scheme programmers use it sparingly and for a reason: a program whose values never change is far easier to read, because you never have to ask what a name holds at this particular moment. When you do reach for it, local state that no other part of the program can see is the tidiest use.
A vector is a fixed row of slots
A vector is one value holding a row of slots, each with a number starting at zero. (vector "Ada" 36) builds one with two values in it, and (make-vector 3 0) builds three slots all holding zero. It prints with a hash sign on the front, which is how you can tell it apart from a list.
vector-ref reads a slot and vector-set! writes one, and that write really does change the value in place — the only place in this course where something other than a name changes. vector-length counts the slots and vector->list hands you the contents as an ordinary list when the row shape is no longer what you want.
State that belongs to one function
A function can hold a private name and hand back another function that uses it. The private name lives as long as the function that was handed back, so calling that function again continues from where the last call left off. This is a counter, a running total, or an id generator, with nothing exposed but the one function.
It is the same set! as above, doing something more interesting: the name it changes is invisible to the rest of the program. Every call to the counter gets its own private value, because each call makes a new name.
What set! really changes
A name points at a value. set! moves that pointer to a different value; it does not edit the old value. Any other name that still points at the old value keeps seeing it.
That is the difference between rebinding and mutation. A vector is one value with slots, so vector-set! changes what everyone holding that vector sees. A list has no slots to change, which is why lists stay safe to share.
The practical rule: use set! for a private name that tracks progress, and vector-set! when a fixed row of slots is the data you have.
Closures keep names alive
When a function is built inside a let, it captures the names visible at that point. Those names stay alive as long as the function does, even after the let has finished.
Each call to the maker creates a fresh set of names, so two counters made by the same function do not share a count. That is the trick behind private state: the number is reachable only through the function that owns it.
Vectors do not get this protection. If you hand the vector out, the caller can write any slot it likes, so a vector is shared state by design rather than private state by accident.
Slots, and a counter of its own
(define v (vector "ada" 36))
(print v)
(print (vector-ref v 0))
(vector-set! v 1 37)
(print v)
(print (vector->list v))
(print (vector-length v))
; A counter that only its own function can change.
(define (make-counter)
(let ((n 0))
(lambda ()
(set! n (+ n 1))
n)))
(define tick (make-counter))
(print (tick))
(print (tick))A vector is for a fixed shape
A vector earns its place when the number of slots is known and the slots are updated in place: a record whose fields change, a row of counters, a small board. A list is better when the shape grows and shrinks, because cons builds a new chain without copying the old one.
Mixing the two is ordinary. A list of vectors is a table of rows; a vector of lists is a row of buckets. Choose the outer shape by how you will iterate, and the inner shape by how you will update.
Three things to watch. Vector slots count from 0, so a three-slot vector has positions 0, 1 and 2, and position 3 stops the program. vector-set! is for the effect, so do not rely on the value it answers. And two names for the same vector see each other’s writes, while two names for the same list do not.
Try these next
- Make two counters and print three ticks from each to show they are independent.
- Build a three-slot vector, set the middle slot, and print it before and after.
- Write a function that returns a
set!-based running total and call it three times.
Three slots and a private counter
Run it and read the seven lines. Then change the counter so it counts down from ten instead of up from zero, and set the middle slot of `scores` to 20 before it is printed.
Two accounts, one function
Run it and check that the two accounts never share a balance. Then add a `withdraw` wrapper that refuses to go below zero, and set the second vector slot through `same`.
Reach for a new value first and a vector second. Building a fresh list costs little and keeps the rest of the program simple; a vector earns its place when you truly have a fixed row of slots, such as a record whose fields change as the program runs.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
In (vector 10 20 30), which call gives 20?
