;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-beginner-reader.ss" "lang")((modname 03-enumerations) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) ;; CS 111 Lecture 3: Enumerations #| BSL comes with a few built-in classes of data: strings, numbers, booleans, and images. To describe all the kinds of information that we might want to talk/program about, though, we will need to define new classes of data. We do this in comments in particular form called a *Data Definition*. One kind of data definition we've seen already just gives a new name to an existing class of data: |# ; A WorldState is a Number #| However, often we will want to define new classes of data that don't correspond directly to existing classes of data. One type of data definition to do this is called an *enumeration*. Here's an example of an enumeration: |# ; A MouseEvt is one of: ; - "button-up" ; - "button-down" ; - "drag" ; - "move" ; - "enter" ; - "leave" #| This means that when we have a MouseEvt, we have one of those six strings. No other value is considered a MouseEvt. When we want to write a function the processes a MouseEvent, we need to figure out which of the six values we've been given. We do that with a cond, which determines which of the six cases we are in. It looks something like this: |# #; (define (process-mouse-event me ...) (cond [(string=? me "button-up") ...] [(string=? me "button-down") ...] [(string=? me "drag") ...] [(string=? me "move") ...] [(string=? me "enter") ...] [(string=? me "leave") ...])) #| This incomplete function `process-mouse-event` is a *template*---that is, a piece of code with holes that we will plug in with specific functionality. Here are a few more enumeration data definitions, along with their templates: |# ; A TrafficLight is one of: ; - "red" ; - "amber" ; - "green" #; (define (process-traffic-light tl ...) (cond [(string=? tl "red") ...] [(string=? tl "amber") ...] [(string=? tl "green") ...])) ; A Grade is one of: ; - "A" ; - "A-" ; - "B+" ; - "B" ; - "B-" ; - "C+" ; - "C" ; - "C-" ; - "D+" ; - "D" ; - "D-" ; - "F" #; (define (process-grade grade ...) (cond [(string=? grade "A") ...] [(string=? grade "A-") ...] [(string=? grade "B+") ...] [(string=? grade "B") ...] [(string=? grade "B-") ...] [(string=? grade "C+") ...] [(string=? grade "C") ...] [(string=? grade "C-") ...] [(string=? grade "D+") ...] [(string=? grade "D") ...] [(string=? grade "D-") ...] [(string=? grade "F") ...])) #| When we write functions on these new classes of data, we use the template to help us distinguish which case we are dealing with. Some examples: |# ; what-to-do : TrafficLight -> String ; Determines what to do at an intersection. ; Example: ; - if red, stop ; - if green, go (define (what-to-do tl) (cond [(string=? tl "red") "stop"] [(string=? tl "amber") "use caution"] [(string=? tl "green") "go"])) (check-expect (what-to-do "red") "stop") (check-expect (what-to-do "amber") "use caution") (check-expect (what-to-do "green") "go") ; next-traffic-light : TrafficLight -> TrafficLight ; Finds the next traffic light after the given one. ; Examples: ; - "red" -> "green" ; - "green" -> "amber" (define (next-traffic-light tl) (cond [(string=? tl "red") "green"] [(string=? tl "amber") "red"] [(string=? tl "green") "amber"])) (check-expect (next-traffic-light "red") "green") (check-expect (next-traffic-light "amber") "red") (check-expect (next-traffic-light "green") "amber") ; GpaPoints is a Number in [0, 4] ; grade->gpa-points : Grade -> GpaPoints ; Converts a grade to its GPA value. ; Examples: ; - "A" -> 4 ; - "A-" -> 3.67 ; - "C" -> 2 (define (grade->gpa-points grade) (cond [(string=? grade "A") 4] [(string=? grade "A-") 3.67] [(string=? grade "B+") 3.33] [(string=? grade "B") 3] [(string=? grade "B-") 2.67] [(string=? grade "C+") 2.33] [(string=? grade "C") 2] [(string=? grade "C-") 1.67] [(string=? grade "D+") 1.33] [(string=? grade "D") 1] [(string=? grade "D-") 0.67] [(string=? grade "F") 0])) (check-expect (grade->gpa-points "A") 4) (check-expect (grade->gpa-points "A-") 3.67) (check-expect (grade->gpa-points "B+") 3.33) (check-expect (grade->gpa-points "B") 3) (check-expect (grade->gpa-points "B-") 2.67) (check-expect (grade->gpa-points "C+") 2.33) (check-expect (grade->gpa-points "C") 2) (check-expect (grade->gpa-points "C-") 1.67) (check-expect (grade->gpa-points "D+") 1.33) (check-expect (grade->gpa-points "D") 1) (check-expect (grade->gpa-points "D-") 0.67) (check-expect (grade->gpa-points "F") 0) #| Note that making a template for an enumeration data definition is purely mechanical. The data definition need not have meaning, and we needn't have understanding, to produce a template from a data definition. For example, suppose you were given this data definition: |# ; A Foo is one of: ; - "bar" ; - "baz" ; - "qux" #| Then it doesn't matter what Foo means. We can write a template for it: |# #; (define (process-foo foo ...) (cond [(string=? foo "bar") ...] [(string=? foo "baz") ...] [(string=? foo "qux") ...])) #| Templates lead us to a new design recipe with an additional step, *strategy*: 1. *** Data definition 2. Signature, purpose, header 3. Examples 4. *** Strategy 5. Coding 6. Tests Also, if we need new data definitions, step 1 is where we should make them. One possible strategy when designing a function is to use a template to analyze one of the arguments based on its structure. As we will see next time, that is one of several possible strategies. |#