In the last lecture, we discussed how to update an entry in an address book: (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))) This solution creates a new list of entries every time update is called. Always creating these new lists seems unnecessary. Ideally, we would like a way to just change the number stored in the entry that is already in the list, without creating a new list or a new entry. DrScheme provides operators for changing the values stored in structures. When you type (define-struct entry (name phone)), DrScheme defines two operators: set-entry-name! : entry symbol -> void set-entry-phone! : entry number -> void These operators allow you to change the values stored in an entry. As an example, consider the following sequence of interactions: > (define-struct entry (name phone)) > (define e1 (make-entry 'kathi 3)) > e1 (make-entry 'kathi 3) > (set-entry-phone! e1 5) > e1 (make-entry 'kathi 5) These operators, which we will call set-structure!, resemble set! in that once you change a value using set-structure!, you can no longer access the previous value. The same cautions about using set-structure! carefully therefore still apply. We can view how DrScheme evaluates a set-structure! in a similar fashion to how it evaluates set!. When we call set-entry-phone! in the above example, it is as if DrScheme goes back to the original (make-entry ...) and changes the value in the structure. Thus, if we have (define-struct entry (name phone)) (define e1 (make-entry 'kathi 3)) and we write (set-entry-phone! e1 5), it is as if we had originally written (define-struct entry (name phone)) (define e1 (make-entry 'kathi 5)) for the remainder of the computation. The set-structure! operators and set! are not exactly the same though. Consider the following program: (define (update-entry! entry new-phone) (set-entry-phone! entry new-phone)) > (define e1 (make-entry 'kathi 3)) > e1 (make-entry 'kathi 3) > (update-entry! e1 5) > e1 (make-entry 'kathi 5) Notice that, unlike set!, structure-set! can affect the values of its parameters. Why is that? The difference lies in the nature of the two parameters. Recall the swap program that we tried to write in the last class: (define (swap x y) (local [(define tmp x)] (set! x y) (set! y tmp))) We said that calling (swap u v) would leave the values of u and v unchanged. This is because we tried to change the values of the parameter, rather than values stored inside the argument passed to the parameter. Had we written (define (update-entry2! entry new-phone) (set! entry (make-entry (entry-name entry)) new-phone)) then update-entry2! would not affect the value of an entry: > (define e1 (make-entry 'kathi 3)) > e1 (make-entry 'kathi 3) > (update-entry2! e1 5) > e1 (make-entry 'kathi 3) Thus, structure-set! can have affects that we could not achieve with set!. For example, we could write a program to swap two numbers inside of a structure: (define-struct coords (x y)) (define (swap-coords a-coord) (local [(define tmp (coords-x a-coord))] (set-coords-x! (coords-y a-coord)) (set-coords-y! tmp))) Using structure-set!, how could we now write the update-address-book program? ;; update-address-book! : symbol number -> void ;; effect: changes the phone number stored with the given name in address book (define (update-address-book! name new-num) (local [(define (helper! a-book) (cond [(empty? helper) void] [else (cond [(symbol=? name (entry-name (first a-book))) (set-entry-phone! (first a-book) new-num)] [else (helper! (rest a-book))])]))] (helper! address-book))) > (define address-book (list (make-entry 'kathi 3))) > address-book (list (make-entry 'kathi 3)) > (update-address-book! 'kathi 5) > address-book (list (make-entry 'kathi 5)) Note that our new update-address-book! does not use set!. Why not? Doesn't it need to set! address-book as we did in the previous version of this program? No. We changed the value inside an existing entry. That change also affects the contents of the list, so we didn't need to use set! explicitly. Let's study this through some examples: > (define e1 (make-entry 'kathi 3)) > (define address-book (list e1)) > address-book (list (make-entry 'kathi 3)) > (set-entry-phone! e1 5) > e1 (make-entry 'kathi 5) > address-book (list (make-entry 'kathi 5)) When DrScheme evaluates (set-entry-phone! e1 5), it's as if we had written > (define e1 (make-entry 'kathi 5)) > (define address-book (list e1)) so the phone number inside e1 gets changed, which affects the contents of address-book. That we gave the entry the name e1 is irrelevant here. We could have written: > (define address-book (list (make-entry 'kathi 3))) > address-book (list (make-entry 'kathi 3)) > (set-entry-phone! (first address-book) 5) > address-book (list (make-entry 'kathi 5)) Moral: you need to be careful when using set-structure! because changes persist beyond program boundaries. As a general rule, update a structure only if the structure represents a persistent object and your program corresponds to a real action. For example, address books are persistent objects and updates to address books represent permanent changes that we wish to make. In contrast, imagine that you were writing a program to play chess and you wanted to see what would happen if you took a particular move. You should not use a set-structure! to change the move because you haven't yet decided to take that move (it's a preliminary, not a real, action). Could we also write update-address-book! using map, filter, or fold? We can't use map or filter because they return lists. We could, however, use fold as follows: (define (update-address-book! name new-num) (foldr (lambda (an-entry result-rest) (cond [(symbol=? name (entry-name an-entry)) (set-entry-phone! an-entry new-num)] [else void])) void address-book)) update-address-book! shows a special case of foldr, one where we want to perform an operation on each element of a list, but we don't want to return a value. Scheme has another built-in abstract function for this purpose, called for-each. For-each consumes a function of one argument and a list. It applies the function to each element of the list and returns void: i.e., (for-each f (list e1 ... en)) = (begin (f e1) ... (f en) void) Using for-each, we could write update-address-book! as follows: (define (update-address-book! name new-num) (for-each (lambda (an-entry) (cond [(symbol=? name (entry-name an-entry)) (set-entry-phone! an-entry new-num)] [else void])) address-book)) We've seen one example of set-structure!: updating phone books. Let's consider another example. In our previous discussion of family trees, we looked at ancestor trees and descendent trees. A real world genealogical database would need trees that could be explored in either direction. Let's define such trees. We must start with a data definition for a node, or person, in the tree. This data definition can't be biased towards ancestors or descendents. ;; A person is a structure ;; (make-person N F M C) ;; where N is a symbol, ;; F and M are person or false, and ;; C is (listof person) (define-struct person (name father mother children)) Let's write a program add-child-name!, which consumes a name, the person structure representing the child's father, and the person structure representing the child's mother. The program should return the new node for the child. ;; add-child-name! : symbol person person -> person ;; creates a node for a child, adds it to the tree, and returns the new node ;; effect : adds a new child node to the list of children in father and mother (define (add-child-name! name father mother) (local [(define child-node (make-person name father mother empty))] (set-person-children! father (cons child-node (person-children father))) (set-person-children! mother (cons child-node (person-children mother))) child-node))