Continuing our study of vectors, let's write a function vector-foreach. Like for-each on lists, vector-foreach should consume a function and a vector and apply the function to every element in the vector, producing no output: ;; vector-foreach : (alpha num[
beta) (vectorof[size N] alpha) -> void ;; applies function f to each element of vector v (define (vector-foreach f v) (local [(define len (vector-length v)) (define (helper index) (cond [(= index len) void] [else (begin (f index (vector-ref v index)) (helper (add1 index)))]))] (helper 0))) > (define v (vector 1 2 3 4 5)) > (vector-foreach (lambda (i n) (add1 n)) v) void > v (vector 1 2 3 4 5) Why didn't this change v? Remember, the call to f is the first statement inside a begin. If f doesn't involve set! or set-structure! (which includes vector-set!), it won't have any effect. We need to fix the function as follows: > (vector-foreach (lambda (i n) (vector-set! v i (add1 n))) v) void > v (vector 2 3 4 5 6) This example demonstrates why f needs to take the index as an argument. If the argument function to vector-foreach uses vector-set!, it has to have the current index. As for lists, we could write a more general abstract function, vector-foldr. As with foldr on lists, vector-foldr can produce any kind of output, based on the base and combine function provided: ;; vector-foldr : ;; ((num[ beta) beta (vectorof[size N] alpha) -> beta ;; combines all elements of vector v using function combine with given :: base. (define (vector-foldr combine base v) (local [(define len (vector-length v)) (define (helper index) (cond [(= index len) base] [else (combine index (vector-ref v index) (helper (add1 index)))]))] (helper 0))) > (define v (vector 1 2 3 4 5)) > (vector-foldr (lambda (index vec-elt res-rest) (vector-set! v index (add1 vec-elt))) void v) > v (vector 2 3 4 5 6) > (vector-foldr (lambda (index vec-elt res-rest) (cons vec-elt res-rest)) empty v) (list 2 3 4 5 6) > What's the difference between vector-foldr and vector-foldl? Vector-foldl starts with an index one less than the length of the vector and stops when the index falls below 0: (define (vector-foldl combine base v) (local [(define len (vector-length v)) (define (helper index) (cond [(< index 0) base] [else (combine index (vector-ref v index) (helper (sub1 index)))]))] (helper (sub1 len)))) Notice the similarities between vectors and lists. For lists, we have build-list and length; for vectors we have build-vector and vector-length. We've now seen three forms of compound data: lists, vectors, and structures. How would you characterize each? lists structures vectors ----------------------------------------------------------------- build-list define-struct build-vector arbitrary size fixed size fixed size accessing ith elt ith elt is ith elt is requires i directly computed directly computed recursive calls (struct-field struct) (vector-ref ...) Do not abuse vectors! People commonly use vectors when lists are more appropriate; sorting is a prime example. Remember, you should only use a vector if your data size is fixed and the natural numbers are a natural way to refer to components of your data. Otherwise, use structures (for small fixed data) or lists! SOME ADDITIONAL POINTS ON OBJECTS Over the weekend, someone asked whether caves (for the current assignment) should be structures or objects. They should be structures, because a cave is a combination of three pieces of information (a name, some items, and the doors). Remember what we use objects for: to group together functions that operated on the same data, where at least one function could modify the data. We grouped the function into an object so that all functions could modify/access the same data structure without that data structure having to be globally accessible. We said that having the data structure be globally accessible was bad because someone else could modify the data structure accidentally. At what point do we actually notice data structure modifications? When we try to run other programs. This raises an important point about programming. When we write programs that interact through data, we can express conditions on those interactions. For the address book programs, for example, lookup should always return the phone number that was most recently added for name. If someone can modify the address book, it's possible to break this requirement. Maintaining these requirements, or invariants, is crucial in programming. In 280, we'll look at how to prove these sorts of requirements about programs. Here's a related problem on objects. We've just said that the point of using objects is to protect information. Consider our banking program again. Here's one with a service for creating new accounts: (define-struct acct (name balance)) (define bank-sys (local [(define accounts empty) (define (new-account! name bal) (local [(define new-acct (make-acct name bal))] (set! accounts (cons new-acct accounts)))) (define (lookup-bal name) ...)] (lambda (msg) (cond [(symbol=? msg 'new) new-account!] [(symbol=? msg 'lookup) lookup-bal] [else ...])))) For extra security, the bank wishes to add passwords to their accounts. Whenever the system creates a new account, it should return a password that the user must send to lookup in order to access the account balance. How would you implement passwords? Here's a new program skeleton: (define-struct acct (name balance passwd)) (define bank-sys2 (local [(define accounts empty) (define (new-account! name bal) (local [(define new-passwd ...) (define new-acct (make-acct name bal new-passwd))] (set! accounts (cons new-acct accounts)) new-passwd)) (define (lookup-bal name passwd) (local [(define match (filter (lambda (acct) (and (symbol=? name (acct-name acct)) (eq? passwd (acct-passwd acct))))))] (cond [(empty? match) false] [else (first match)])))] (lambda (msg) (cond [(symbol=? msg 'new) new-account!] [(symbol=? msg 'lookup) lookup-bal])))) > (define my-password ((bank-sys2 'new) 'kathi 100)) > ((bank-sys2 'lookup) 'kathi my-password) 100 The trick here is to fill in the definition of new-passwd. What could you use? You want something that can't be guessed -- someone can only access a password by stealing the password that new-account! returns. This rules out using numbers, because someone could write a program to try numbers in order until they got to my password. The problem here is that eq? works on numbers. For passwords to work, we need a value that is only eq? if I have the exact password returned when my account was created. This suggests structures. If I define (define-struct passwd ()) and write (make-passwd) in place of the ..., then I get a unique password. This works because (eq? (make-passwd) (make-passwd)) is false. Thus, uniqueness of structures provides a way to identify and distinguish structures. This example is meant to highlight this feature of eq? on structures, as opposed to eq? on numbers or other atomic data (such as booleans or symbols).