Lecture 22 : Mutable structures When we made deposits into bank accounts, we did it by constructing the whole list afresh and then saving the answers. That still seems like more work than necessary. Couldn't we have just changed the data in _one_ account instead? As a reminder, here's the data definition we used for accounts: ;; An account is (make-account number number) (define-struct account (acctnum balance)) Let's try to write a function that uses set! to deposit money into a single account ;; deposit-into : account number -> void ;; adds the given amount to the account balance ;; EFFECT: increases the balance in the account (define (deposit-into acct amt) (set! (account-balance acct) (+ amt (account-balance acct)))) This won't work because set! only works on variables, and (account-balance acct) isn't a variable. Surely there is a way to change what's inside a structure though? When you use define-struct, Scheme also gives you operators for changing the contents of structures. Specifically, for the account structure defined above, you get set-account-acctnum! : account number -> void set-account-balance! : account number -> void Using these, we could write deposit-into as ;; deposit-into : account number -> void ;; adds the given amount to the account balance ;; EFFECT: increases the balance in the account (define (deposit-into acct amt) (set-account-balance! acct (+ amt (account-balance acct)))) [test this for yourself] Using deposit-into gives another way that we could write the deposit function over a list of accounts: ;; deposit : number number list-of-account -> void ;; consumes account number, amount of money, and list of accounts and ;; produces list of accounts with given amount added to named account (define (deposit2 acctnum amt aloa) (cond [(empty? aloa) (error 'deposit "No such account")] [(cons? aloa) (cond [(= acctnum (account-acctnum (first aloa))) (deposit-into (first aloa) amt)] [else (deposit2 acctnum amt (rest aloa)))])]) Note that this version differs from the original in two key ways: - it returns void, not a list of account - the conses are gone (not rebuilding the list) [test this for yourself] So why didn't I show you this before? Wouldn't this have been the right way to write programs like deposit from the beginning, rather than by rebuilding all the lists? We didn't do this earlier because set! and (especially) set-structure! can have bad consequences since they redefine (ie DESTROY) data. We don't always want to destroy data. We taught you a safe way to write programs for starters; now we start looking at how to program when the language requires us to be careful. Are set! and set-structure! really interchangeable as deposit suggests? Yes for this example (and the homework), but not in general. To see why, let's consider an example. Assume the bank wants to store personal info on account holders as well their accounts. They create the following data definition: ;; A customer is (make-customer string number account) (define-struct customer (name phone acct)) Now assume that two customers, Maria and Phil, are married and share an account (but have different cell-phone numbers). How might we create the data for them as customers? Approach 1 : (define Mcust (make-customer "Maria" 5553245 (make-account 24 500))) (define Pcust (make-customer "Phil" 5556702 (make-account 24 500))) This seems wrong from the start, because they aren't sharing the account -- they have two different accounts with the same number and balance. If we change Maria's balance by writing (set-account-balance! (customer-acct Mcust) (+ amt (account-balance (customer-acct Mcust)))) set-structure! needs something that evaluates to a structure as the first argument, so this code is fine. Then look at both Mcust and Pcust, we see that Phil's account hasn't changed. Let's try what most of you would have done in the first place: Approach 2 : (define MPacct (make-account 24 500)) (define Mcust (make-customer "Maria" 5553245 MPacct)) (define Pcust (make-customer "Phil" 5556702 MPacct)) Now let's change the balance and see what happened: (set-account-balance! MPacct 1000) The change shows up in both Mcust and Pcust. What if we change the balance going through Maria's customer structure instead of by modifying MPacct by name? (set-account-balance! (customer-acct Mcust) (+ amt (account-balance (customer-acct Mcust)))) That still does what we want it to, because the same account structure is inside both of Mcust and Pcust. What if we used set! instead, and wrote (set! MPacct (make-account 24 2000)) Note that now the change doesn't show up in _either_ Mcust or Pcust. This is because the set! redefines tne _name_ MPacct, but what Scheme stores inside Mcust and Pcust isn't the name MPacct, it's the actual structure. This highlights the difference between set! and set-structure! set! changes what a name refers to but not the data associated with that name, while set-structure! actually changes the contents INSIDE the structure. This also suggests rules for choosing between them: If the same structure is used in multiple places and you want to change its contents in all those places, use set-structure! on the structure. If you want to change it only one place, substitute the structure with a new structure (as set! will do). Structures used in only one place can be changed using either approach (set-structure! on the structure or set! on the variable that contains the structure). Returning to our list of accounts, consider a function close-account that consumes an account number and removes that account from the list of accounts. Should we write it with set! or set-structure!? ;; close-account : number -> void This has to use set! because we are not changing the contents of one account, but rather modifying the set of accounts overall. set-structure! operators only work on structures, not on variables in general. Given a list of structures, always ask yourself whether the needed change is local to one structure or pertains to the whole list. Local structure changes use set-structure!; list changes use set!. Functions that change the list often need a helper function to produce the new list; the main function uses set! to save the result of the helper.