Quick jump to discussion of lists
Let's write a function that computes the maximum number of overnight guests that a person accept:
(Assume we've already done our data definition for people, home, house and apt.)
Remember our template for dealing with a person structure from the last lecture:
(define (aPersonFunc ...aPerson...)
(cond
( person? aPerson) ( ...(person-name aPerson)......(person-age aPerson)...(person-home aPerson))]
[else "Invalid Input"]))
For convenience sake, I will define save some instances of struct by defining references to them:
(define sw (make-person 'Stephen
42 (make-apt "512 Lost Trail" 1010 1)))
(define ib (make-person 'Ian 39 (make-house "2048 Happy Way" 5 3)))
Aside:"sw" and "ib" are references to their respective person structures, they are not the actual structures themselves. They simply allow us to repeatedly refer to the same data. Consider (define sw_evil_twin sw) -- does this create a twin or is this really a Dr. Jekyl and Mr. Hyde situation? |
Continuing on....
So let's start the process here:
I want my function, call it, maxGuests, to take a person and return a statement about the maximum number of overnight guests that can be handled.
The result of the function should thus be a string like: "[person's name]'s home can handle at most X guests."
That is, I have the following two test cases:
(string=? "Stephen's home can handle at most 2 guests." (maxGuests
sw)) (string?= "Ian's home can
handle at most 12 guests." (maxGuests ib))
Now, we know that we will need some functions to process the person's home (since we don't want to violate the home's encapsulation as we discussed last time).
The question is what does that function do?
We figure that one out be not by violating the home's encapsulation and digging into home processing algorithms but rather by staying at the abstraction level that the original function on a person is working and understanding what its needs are.
That is, we write the function on a person first, calling what at the moment is a mythical function on a home, that returns exactly what it needs. In particular, we will delegate the responsibility of calculating the maximum number of guests to the function on the home, because that is really a function of a home, not a function of a person.
; maxGuests: aPerson -> string
;; This function calculates the maximum number of overnight guests that a person can handle in their home.
;; aPerson is a person structure and the resultant string is of the form
;; "[person's name]'s home can handle at most X guests."(define (maxGuests aPerson)
(string-append (symbol->string (person-name aPerson)) "'s home can handle at most " (maxGuestsHome(person-home aPerson)) " guests."));; Test cases
(string=? "Stephen's home can handle at most 2 guests." (maxGuests sw))
(string?= "Ian's home can handle at most 12 guests." (maxGuests ib))
Style note: I had emphasized before that error checking for incorrect input types was important and for real-world applications, it is extremely important. However, in the interests of simplifying code to emphasize the fundamental structure of the algorithms, we will run on the assumption that the inputs are of the correct type -- this the mathematician's view of programming, which ignores real-world mistakes. In more advanced courses, such as Comp212, you will learn how to make programs that are naturally "robust", that is, that can intrinsically handle errors that are thrown at them.. Later in this course, we will show you some mechanisms that Scheme has to handle error conditions, but we'll wait until we are more comfortable with coding. In the mean time, you may leave out input type checking (you will not be penalized for including it), unless explicitly instructed to check for particular errors. Of course, you must still always check for the sub-types of an abstract data type! |
In order for the above function to work correctly, the function maxGuestsHome must be defined as returning a string that represents the maximum number of overnight guests that a house can handle.
Let's make some assumptions on guests and homes:
It is extremely important that these assumptions be part of the documentation of the function!
We thus have the following function:
;; maxGuestsHome: aHome -> string #|
|# ;; where aHome is a home structure
and the result is a number in string format. (define (maxGuestsHome aHome)
(number->string (cond
[(house? aHome) (+ 4 (* 2
(- (house-nBed aHome) 1)))]
;; Calculates the maximum number of guests that a home can handle under
the following assumptions:
[(apt? aHome) (+ 2 (* 2 (- (apt-nBed aHome) 1)))])))
(string=? "42" (maxGuestsHome (make-house "1600 Penn Ave." 20 10)))
(string=? "4" (maxGuestsHome (make-apt "64 Garden Drive" 256 2)))
The key issue here is to think of the implementation of maxGuestsHome independently from the abstract behavior that it represents.
The maxGuests function works at the higher abstraction level of a person and homes, while maxGuestsHome works at a lower abstraction level of specific issues concerning concrete types of homes, e.g. house, apt, etc.
The complete code for the above example can be downloaded here: maxguests.scm
Since structures can hold other structures, couldn't a structure hold one of its type? Why not?
I liked Dr. Barland's discussion on this subject so much that I'll just have to re-print it here in all its glory:
[Suppose that an] airline wants to keep .. detailed servicing records and decides to record the last three mechanics who serviced [a] plane. How would we do that?
(define-struct 3mechs (recent two-ago three-ago)) ;; ;; A 3mechs is ;; (make-3mechs symbol symbol symbol) ;; where recent is the most recent inspector's name, ;; two-ago did the inspection before that, and ;; three-ago the one before two-ago.What is a particular example of this type of data? How would we record that the last three mechanics to service a plane were Mike, Patty, and Bubba?
(make-3mechs 'Mike 'Patty 'Bubba)Could you write a function which processes 3-mechs? (What does its template look like?) Solution:
(define (process-3mech a-3m) ..(3mechs-recent a-3m)..(3mechs-two-ago a-3m)..(3mechs-three-ago a-3m)..)Think to yourself: how would you write the function update-mechs: 3mechs, symbol --> 3mechs?
(define (update-mechs a-3m newest-mech) ..(3mechs-recent a-3m)..(3mechs-two-ago a-3m)..(3mechs-three-ago a-3m)..) ; Tests (update-mechs (make-3mechs 'Mike 'Patty 'Bubba) 'Mike) = (make-3mechs 'Mike 'Mike 'Patty)
To think about quickly: What is the problem with this approach of keeping
track of the last 3 mechanics? Exactly -- what if the plane is new, and hasn't
even been serviced 3 times? If we're lazy, we use the symbol 'not-yet-serviced.
However, this precludes hiring somebody of this name!? (What if we were talking
about album names, and used the flag 'self-title?) When using a sentinel
value, be sure it doesn't conflict/confused with a possible real value!
Better solution: a new data definition, where name cannot possibly be confused
with the sentinel value:
;; A symbol-or-false is ;; - a symbol, or ;; - false ;; ;; A 3mechs is: ;; (make-3mechs symbol-or-false symbol-or-false symbol-or-false)
What if the airline wanted to track all mechanics who had ever serviced a plane, not just the last three? What struct would you define? The problem is that define-struct fixes the number of items that you glue together. However, we don't know how many mechanics will service a plane, so we can't define the structure.
We need a way to glue together arbitrarily many names of mechanics. What do you do when you want to keep track of many pieces of information? You make a list. Let's write down the mechanics who last serviced a particular plane:
Eddie Mike Patty BubbaWe need a way to turn this list into data for DrScheme. Let's start simple. What's the smallest list you can think of? Empty list. In Scheme, we write empty for the empty list. Now, we need a way to create non-empty lists.
Let's look at the list on the board. We agree that what's there is a list. Is
Mike Patty BubbaA list? What's the relationship between these two lists? The first has added a name onto the second. Therefore, we can think of a list as having two parts: the name at the top and the rest of the list. Can you define a struct to capture these two pieces of information?
;; A structure for a cons ("constructed list"): (define-struct cons (first rest))Using this struct, how would you create a list containing Bubba?
(make-cons 'Bubba empty)Before we continue, let's write down the data definition for a list of symbols.
;; A List-of-Symbols is either ;; - empty, or ;; - (make-cons symbol List-of-Symbols) ; Recall: this creates the functions ; make-cons, ; cons-first, ; cons-rest, and ; cons? ; ;; Examples of data: empty (make-cons 'Bubba empty) (make-cons 'Mike (make-cons 'Patty (make-cons 'Bubba empty)))Since structures are just boxes with a drawer ("field") for each piece of data, here's the picture in my mind:
(make-cons 'Bubba empty) +==================+ # first: 'Bubba # # ~~~~~~~~ # # rest: empty # # ~~~~~~~~ # +==================+ (make-cons 'Patty (make-cons 'Bubba empty)) +==================================+ # first: 'Patty # # ~~~~~~~~~~~~~~~~~~~~~~~~ # # # # +==================+ # # # first: 'Bubba # # # # ~~~~~~~~ # # # # rest: empty # # # # ~~~~~~~~ # # # rest: +==================+ # # ~~~~~~~~~~~~~~~~~~~~~~~~~~ # +==================================+ (make-cons 'Mike (make-cons 'Patty (make-cons 'Bubba empty))) +====================================================+ # first: 'Mike # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # # # +==================================+ # # # first: 'Patty # # # # ~~~~~~~~~~~~~~~~~~~~~~~~ # # # # # # # # +==================+ # # # # # first: 'Bubba # # # # # # ~~~~~~~~ # # # # # # rest: empty # # # # # # ~~~~~~~~ # # # # # rest: +==================+ # # # # ~~~~~~~~~~~~~~~~~~~~~~~~~~ # # # rest: +==================================+ # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # # +====================================================-+ (Another picture can be found in the text.)
(Dr. Wong notes: The UML-style diagram of the above looks like this:)
The blue boxes are the cons list structure, holding a first and a rest, which is a reference to another abstract list, which may be either cons or empty.
How would you extract Mike out of this list?
(cons-first (make-cons 'Mike (make-cons 'Patty (make-cons 'Bubba empty)))How would extract Patty out of this list? (Hint: first, ask how you'd get the list containing everybody but mike, and work from there.)
(cons-first (cons-rest (make-cons 'Mike (make-cons 'Patty (make-cons 'Bubba empty)))Note: I write each of the two arguments to make-cons on its own line, for clarity. You can do this if you like, or you can put them all on one line, that's fine.
Trick question: what is (cons-first empty)? Yep, it's an error. Why is that a trick question? Because empty is not a constructed list! Be sure to think over the difference between lists vs. empty or make-conss. It's comparable to the difference between flying-objects vs planes or ufos or peaches or gnats.
Okay, let's try writing functions! What happens to update-mechs: List-of-Symbol, symbol --> List-of-Symbol? Well, if you think about it, this is exactly make-cons.
Note: Lists are used commonly enough, that the define-struct for cons is already built-in.
Name you'd expect | name in Scheme | name in Lisp |
---|---|---|
make-cons | cons | cons |
cons-first | first | car |
cons-rest | rest | cdr |
cons? | cons? | |
empty | nil, '() | |
empty? | null? | |
false | #f, nil | |
true | #t |
Dr. Barland's code for the above discussion can be found here:lists_intro.scm
(Dr. Wong notes:)
Is the template for lists any different than that for any other compound structure? Do you see why it was so important not to violate the encapsulation of the composed (held) structure?
Could the processing of the rest of the list call the very function we are defining? How might this be useful? In these situations, could we add anything to the template? You'll find out in lab!
©2002 Stephen Wong