A small library, end to end
An address book built from everything in the course: records, a table lookup, a recursive walk, text formatting, and one controlled escape.
What the library is made of
The program builds an address book: a list of records, where each record is a list of a name and a number. Adding someone means putting a new record on the front of the book, which is one cons. Looking someone up means walking the list until the name matches, which the built-in assoc already does for us.
Everything else is presentation: a function that turns the records into printable labels. There is no new idea in this chapter — every piece has appeared already, and that is the point. A program this size is a composition, not a new trick.
How the pieces fit
add takes a book and answers a new book with one more record in it. lookup searches that book and answers either the number or a friendly string saying it was not found — a good habit, because the caller then does not have to check for a missing value before printing it.
labels is the formatting step: map over the records, string-append to build each line, reverse so the newest entry prints last. Then the program prints the labels one per line with for-each and print.
One last idea: leaving early
There is one more form worth meeting before you finish, because it is the idea Scheme is most famous for. call/cc hands the function it is given one extra argument: a way out. Call that argument with a value, and the whole call/cc expression answers with that value while everything still to do inside is abandoned.
Used here it is an early exit from a search: the moment a match is found, the answer is handed to the way out and the remaining values are never looked at. In full generality this is one of the deepest ideas in the language, and this small search is as far as this course takes it.
A program is layers of small answers
The address book works because each function answers one thing: add answers a book, lookup answers a number or a message, labels answers a list of lines. No function needs to know how the others are built, only what they answer.
That is what makes the program extendable. A new field means changing the record shape and the functions that read it, not rewriting the search. A new output format means changing labels, not the data.
The shape of the data is the real decision. A record as a list is quick to build and read by position; a record as a vector is quicker to update but harder to read; a table of pairs is right when you look things up by key.
Finishing, and checking your work
A small program is finished when you can predict its output before you run it. That is a stronger test than seeing output that looks plausible, because it catches the case where two mistakes cancel out.
Take the program apart deliberately: remove the reverse, change a name, make a lookup miss. Each change should produce the output you expect. When it does not, you have found the line you did not understand.
The same habit scales. Most debugging is narrowing the gap between what you believe a line does and what it actually does, and a program small enough to hold in your head is the best place to practise it.
A search that stops the moment it finds one
; call/cc hands the function the rest of the computation.
(define (first-even xs)
(call/cc
(lambda (return)
(let loop ((rest xs))
(cond ((null? rest) #f)
((even? (car rest)) (return (car rest)))
(else (loop (cdr rest))))))))
(print (first-even (list 1 3 4 5)))
(print (first-even (list 1 3 5)))Predict, then run
The last habit worth taking from this course is prediction. Before pressing Run, say what each line will print. The moment your prediction and the output disagree, you have found a belief about the language that is wrong, which is worth more than ten correct runs.
Keep the program small enough for that to be possible. When it grows past a page, split it into functions whose answers you can predict separately, and the whole becomes predictable again.
Three ways a composed program breaks. A record built with two fields but read as three stops at list-ref. A lookup that answers #f is fine until the next line takes it apart without checking. And a map over the wrong list gives a plausible answer of the wrong length, which is harder to see than an error.
Try these next
- Add a third field to each record and update
labelsto print it. - Write a
countfunction that answers how many records the book holds. - Make
lookupanswer a default number instead of a message and adjust the caller.
The address book
Run it and read the four lines. Then add a third person, change the not-found message, and make the labels print the number first with the name after it.
A course report
Run it and read the total and the filtered titles. Then add a fourth course, and change `long-titles` to keep titles of four letters or more.
Read the output and then break it deliberately: remove the reverse and watch the order change, or delete the (if row ...) and see the pane answer with an [error] line. A program you have taken apart once is a program you understand.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does (assoc name book) answer when the name is in the book?
