;; 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 lect09-structs1) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ()))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Union types: ;;; A data-type that can be of type A OR B. ;;; To handle them: use a `cond`, with one branch per type. ;;; A second example: ;;; the rank of a playing-card. ;;; (We'll deal with the suit momentarily.) ; A rank is either: ; - an integer ; or - a symbol ; Examples of the data, for rank: 5 'queen #| The above steps -- thinking about what data types we'll use to represent the information we want -- are the mysterious missing steps 1 and 2 of the design recipe! >>> 1. Choose data representation >>> 2. Give some examples of the data 4. write test cases 5 (a-d): header, purpose, contract/type, stub-return 7. complete the body 8. run tests |# ; Reminder/review: two hepful functions, using symbols: ; symbol=? : symbol, symbol -> boolean ; symbol? : ANY -> boolean ; These are pretty much the *only* functions dealing with symbols! ;;; Review/Practice: Writing code to handle a union type: ;;; test cases (write 'em first!) (check-expect (rank->string 5) "5") (check-expect (rank->string 10) "10") (check-expect (rank->string 'queen) "Q") ;; A stub function: ; rank->string: rank -> integer ; #;(define (rank->string r) "whoa") ;;; Since we're handling data that can be integer OR symbol, ;;; use a cond with two branches: ; func-for-rank: rank -> ?? ; #;(define (func-for-rank cr) (cond [(integer? cr) ...] ; what functions want to process an integer? [(symbol? cr) ...])) ; what functions want to process a symbol? ; ; Note: No description needed -- name is self-descriptive, ; which is usually the case for conversion-functions. ;;; This is a **New Template Step**, 6a: ;;; For handling union types, include a cond with one branch per option. #| Follow the design recipe: 1. Choose data representation 2. Give some examples of the data 4. write test cases 5 (a-d): header, purpose, contract/type, stub-return >>> 6. a. If handling a union type, include a cond w/ one branch per option. 7. complete the body 8. run tests |# ;;; Now, procede with template step 7, completing the body: ; rank->string : rank -> string ; (define (rank->string r) (cond [(integer? r) (number->string r)] [(symbol? r) (string (char-upcase (string-ref (symbol->string r) 0)))])) ; ; Btw: Coming up with these particular conversion functions ; is not critical; that's a function of being familiar ; with the various string and character library functions. ;;; Template Step 8: run tests; make sure they pass. ;;; Review: ;;; If you have a union-type ;;; ("A Blargh is either: ;;; - a char, *or* ;;; - an int, *or* ;;; - a boolean"), ;;; your code that processes a Blargh will naturally start with ???? ;; By the way, if we write 20 different functions dealing with `rank`s, ;; there is a bunch of code that will always appear. ;; We call that the TEMPLATE for functions that consume a `rank`: ; func-for-ranks : rank -> ?? ; #;(define (func-for-ranks r) (cond [(integer? r) ...] [(symbol? r) ...])) ;;;;;;;;;;;;;;;;; Next stage: define-struct ;;; (a "product type" -- cartesian product of sets) ; ;; Suppose we want to write a program dealing with playing-cards. ;; Steps 1 of the design recipe: What data type to use? ;; Unfortunately, any of our known types don't quite do it. ;; ;; New concept: structures (a.k.a. classes-without-fields) -- ;; bundle up several smaller pieces of data, and conceptually call ;; it *one* piece of data (which contains other data inside itself). ;; For example, a card is a rank *and* a suit. ;; We call this a "product type", since it is the cartesian ;; product of two sets(types). ;; Let's see the racket mechanism, for defining structures: ;; `define-struct`. ; My data definition: ; (define-struct card (r s)) ; r : rank ; s : symbol ; Examples of the data: ; (make-card 4 'spades) (make-card 'queen 'hearts) ; Actually, for convenience, I'll bind the above values ; to identifiers, for easy future use. ; (But realize, that using `define` is orthogonal ; to having/creating values of the given type). ; (define 4s (make-card 4 'spades)) (define qh (make-card 'queen 'hearts)) ; When I define-struct, racket creates four functions for me: ; constructor: make-card : rank, suit -> card ; accessor: card-r : card -> rank ; accessor: card-s : card -> suit ; predicate: card? : any -> boolean ; have-a-heart : rank -> card ; Return a card of the given rank, in hearts. ; (define (have-a-heart r) (make-card r 'hearts)) (check-expect (have-a-heart 10) (make-card 10 'hearts)) (check-expect (have-a-heart 'ace) (make-card 'ace 'hearts)) ; On-the-spot question: ; Suppose I have ; (define-struct book (title pages author)) ; ; (where `pages` is the number of pages). ; With a friend, write down: ; (a) the signature of the constructor ; (b) How to construct Moby Dick (by Herman Melville, 487 pages) ; (You can assign that struct the identifier `md` if you like.) ; (c) The signature of any one of the three accessors ;;;;;;;;;;;;;; (will probably not reach here Fri., but...) ;;;;;;;;;;;;;;; ; Write a function dealing with `card` structs. ; (check-expect (card->string (make-card 'ace 'spades)) "AS") (check-expect (card->string (make-card 10 'clubs)) "10C") (check-expect (card->string 4s) "4S") (check-expect (card->string qh) "QH") ; card->string : card->string ; Return a human-readable string, representing a card. ; #;(define (card->string c) "whoa") ; Recipe, step 6b: If processing a struct, ; first pull out each piece of the struct, and remind yourself of its type: #;(define (func-for-card c) ...(card-r c)... ; this is a rank; what functions want to process a rank? ...(card-s c)...) ; this is a suit; what functions want to process a rank? ;;; At this point, we realize that the bit of code buried inside rank->string (which ;;; took a symbol, grabbed the first letter, and up-cased it) is helpful again here. ;;; We re-factor our code to pull that out into (say) `first-char-of` or `symbol->string1` or ... ; symbol->string1 : symbol -> string ; Return a one-character string: `s`s first letter, upcased. ; (define (symbol->string1 s) (string (char-upcase (string-ref (symbol->string s) 0)))) (check-expect (symbol->string1 'hello) "H") (check-expect (symbol->string1 'z) "Z") (check-expect (symbol->string1 'Z) "Z") ;; TODO: go back to `rank->string` and replace duplicated code w/ a call to `symbol->string1`. ;; Make sure test cases still pass (super-easy to test, since we have check-expects built in). (define (card->string c) (string-append (rank->string (card-r c)) (symbol->string1 (card-s c)))) #| We just saw an example of using step 6b of design recipe: 6b. If handling a product-type, pull out each piece (field); think about what type it is, and some functions that process that type. Follow the design recipe: 1. Choose data representation 2. Give some examples of the data 4. write test cases 5 (a-d): header, purpose, contract/type, stub-return 6. a. If handling a union type, include a cond w/ one branch per option. >>> b. If handling a product-type, pull out each piece (field); think about what type it is, and some functions that process that type. 7. complete the body 8. run tests |#