To review the use of local to hide variables, and see how this leads
naturally to objects, let's try building a simple
counter. [See the linked file for all programs pertaining to the
counter example]
In the real world, we often extend code that we've already written
with new functionality. How hard is it to add a new service to the
address book? Not hard at all. We add the program for the new
service to the local and add a cond clause to return the new service.
As an example, let's add an update service. Update should consume a
name and a number and replace the person's entry in the phone book
with the new entry. Writing an update function is easy
(straightforward structural recursion).
;; address-ops : symbol -> address-book-service
;; returns the program for the requested service
(define address-ops
(local [(define address-book empty)
(define (lookup-number name)
(local [(define matches
(filter (lambda (an-entry)
(symbol=? name (entry-name an-entry)))
address-book))]
(cond [(empty? matches) false]
[else (entry-number (first matches))])))
(define (add-to-address-book name num)
(begin
(set! address-book
(cons (make-entry name num) address-book))
true))
(define (update-address name num)
(set! address-book
(map (lambda (entry)
(cond [(symbol=? (entry-name entry) name)
(make-entry name num)]
[else entry]))
address-book)))]
(lambda (service)
(cond [(symbol=? service 'lookup) lookup-number]
[(symbol=? service 'add) add-to-address-book]
[(symbol=? service 'update) update-address]))))
> ((address-ops 'lookup) 'Kathi)
3
> ((address-ops 'update) 'Kathi 5)
> ((address-ops 'lookup) 'Kathi)
5
Now that your new address book program is finished, you and your
roommate decide that you each want your own address book. You have
your program [address-ops]. How do you give your roommate an
address book that won't conflict with yours? One option is to copy
the code and change the function name to address-ops2, but that
copies a lot of code. Avoid copying code when possible. If you add a
new feature to (or correct a mistake in) your address book program,
you don't want to have to make that same edit to every copy of the
program that you've handed out.
We approach this problem the same way we approach any situation with
duplicated code: create a helper function that has as parameters any
information that differs across the two uses of code. In this case,
there are no differences, so the helper function takes no arguments:
;; make-address-book : -> (symbol -> address-book-service)
;; returns a new address book program
(define (make-address-book)
(local [(define address-book empty)
(define (lookup-number name)
(local [(define matches
(filter (lambda (an-entry)
(symbol=? name (entry-name an-entry)))
address-book))]
(cond [(empty? matches) false]
[else (entry-number (first matches))])))
(define (add-to-address-book name num)
(begin
(set! address-book
(cons (make-entry name num) address-book))
true))
(define (update-address name num)
(local [(define updated-book
(map (lambda (entry)
(cond [(symbol=? (entry-name entry) name)
(make-entry name num)]
[else entry]))
address-book))]
(set! address-book updated-book)))]
(lambda (service)
(cond [(symbol=? service 'lookup) lookup-number]
[(symbol=? service 'add) add-to-address-book]
[(symbol=? service 'update) update-address]))))
> (define Kathi-book (make-address-book))
> (define John-book (make-address-book))
> ((Kathi-book 'add) 'Tom 4)
true
> ((Kathi-book 'lookup) 'Tom)
4
> ((John-book 'lookup) 'Tom)
false
What is make-address-book? It's something that can be used to create
objects. In other words, it's a class.