;; 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 lect11-structs2) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ()))) ;;;;; lect11-structs2.rkt ;;; Code from last time: ; A rank is either: ; - an integer (2..10) ; or - a symbol ('jack, 'queen, 'king, 'ace) ; Examples of data: 2 10 'queen 'ace ; Template, for any function handling `rank`s: #;(define (func-for-ranks a-r) (cond [(integer? a-r) ...] [(symbol? a-r) ...])) ;;; In bridge: ;;; you assign "bridge values" to card-ranks: ;;; most cards are 0, but jack is 1, queen is 2, king is 3, ace is 4. ;;; test cases (write 'em first!) (check-expect (rank->pts 5) 0) (check-expect (rank->pts 'queen) 2) ; rank->pts : rank -> int ; compute the bridge-playing value for a given rank. (define (rank->pts a-r) (cond [(integer? a-r) 0] [(symbol? a-r) (cond [(symbol=? a-r 'jack) 1] [(symbol=? a-r 'queen) 2] [(symbol=? a-r 'king) 3] [(symbol=? a-r 'ace) 4])])) (check-expect (rank->string 5) "5") (check-expect (rank->string 10) "10") (check-expect (rank->string 'queen) "Q") ; rank->string : c-rank -> string ; (define (rank->string a-cr) (cond [(integer? a-cr) (number->string a-cr)] [(symbol? a-cr) (symbol->string1 a-cr)])) ; 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") ; OPTIONAL: ; Btw, we can write: ; rank? ANY -> boolean ; (define (rank? any) (or (and (integer? any) (<= 2 any 10)) (symbol? any))) ; ; The design recipe: ; http://htdp.org/2003-09-26/Book/curriculum-Z-H-7.html#node_sec_4.4 ; --- per (abstract) data type: ; - data def'n ; - examples of data ; - template (case analysis) ; --- per function: ; - contract, purpose, header ; - test-cases (check-expect) ; - complete function-body ; - case analysis (cond-questions, 1 per data variant) ; - answer for each cases ; - run the tests (trivial) ;;;;;;;;;;;;;;;;; Next stage: define-struct (product type, compound) ; ; Example: (define-struct card (r s)) ; r : rank ; s : symbol (define 4s (make-card 4 'spades)) (define qd (make-card 'queen 'diamonds)) #| The corresponding Java: class Card { Rank r; String s; } ... new Card( 4, "hearts" ) ... |# ; When I define-struct, racket creates four functions for me: ; make-card -- constructor ; card-r -- accessor ; card-s -- accessor ; card? -- predicate ; have-a-heart : rank -> card ; Return a card of the given rank, in hearts. ; (define (have-a-heart a-cr) (make-card a-cr 'hearts)) (check-expect (have-a-heart 3) (make-card 3 'hearts)) (check-expect (have-a-heart 'jack) (make-card 'jack 'hearts)) #| In Java: static Card haveAHeart( Rank aCr ) { return new Card(aCr, "hearts"); } |# ; Template for any card-processing function: ; #;(define (func-for-card c) ...(card-r c) ; type: rank; rank->pts rank->string ...(card-s c)) ; type: symbol; useful functions: symbol=? ; card->pts : card -> number (define (card->pts c) (rank->pts (card-r c))) (check-expect (card->pts qd) 2) (check-expect (card->pts 4s) 0) (check-expect (card->pts (make-card 'ace 'clubs)) 4) ; turn-to-spades: Given a card, return a new card of the same rank, and suit 'spades. ; ; turn-to-spades : card -> card (define (turn-to-spades a-card) ;...(card-r a-card)... ; type: rank ;...(card-s a-card)... ; type: symbol (make-card (card-r a-card) 'spades)) (check-expect (turn-to-spades (make-card 7 'hearts)) (make-card 7 'spades)) (check-expect (turn-to-spades (make-card 7 'spades)) (make-card 7 'spades)) #| In Java: static Card turnToSpades( Card aCard ) { return new Card( aCard.r, "spades"); } or Card turnToSpades( /* Card this, */ ) { return new Card( this.r, "spades"); } Note: WE ARE NOT CHANGING/ASSIGNING TO ANY FIELDS! We're writing Card to be "immutable data" (just like class String). |# #| 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). c. Inventory: In all cases, think about what *type* (each piece) is, and think of some functions that process that type. 7. complete the body 8. run tests |# ;;; What about: A structure that contains other structures? ;;; No prob! ; A hand is (say) two cards, plus whether or not ; those cards has been revealed to all: (define-struct hand (c1 c2 revealed?)) ; A hand is: ; (make-hand [card] [card] [boolean]) ; What four functions does this `define-struct` create for us? #;{ ; make-hand : card, card, boolean -> hand ; hand-revealed? : hand -> boolean ; hand-c1 : hand -> card ; hand-c2 : hand -> card ; hand? : any -> boolean } ;;; examples of the data: (define h1 (make-hand 4s qd true)) (make-hand 4s qd (not (symbol=? 'hello 'bye))) (make-hand (make-card 4 'spades) (make-card 'queen 'diamonds) true) (define h2 (make-hand (make-card 'queen 'diamonds) (make-card 4 'spades) false)) ;; func-for-hand: hand -> ...??... ;; #;(define (func-for-hand h) ... (hand-c1 h) ; card turn-to-spades card->string ... (hand-c2 h) ; card turn-to-spades ... (hand-revealed? h)) ; boolean cond, if, or, and, not ;;; Step: inventory the fields (what type is each), ;;; and think about functions we have, that want to process that type. ;;; ;;; HOWEVER: for fields that are themselves ;;; further union and/or product types, DO NOT process those here; ;;; instead, call a helper that *just* deals with that type. ;;; ;;; Principle: Separation of Concerns: This function only deals with one type, ;;; (and uses that template); delegate to other funcs, to handle other types. (check-expect (hand->string h1) "4S,QD") (check-expect (hand->string h2) "face-down") (define (card->string crd) (string-append (rank->string (card-r crd)) (symbol->string1 (card-s crd)))) ;; hand->string : hand -> string ;; (define (hand->string h) (if (hand-revealed? h) (string-append (card->string (hand-c1 h)) "," (card->string (hand-c2 h))) "face-down")) ;;; Note that we can think of `if` as a function (!): ;;; if : boolean, α, α -> α // (hmm, is that reall the best type?) ;;; ;;; Okay, honestly, it's not *quite* a function: ;;; (define n 0) (if (zero? n) 15 (/ 1 n)) does never divides by 0. ;;; ;;; Java has the `... ? ... : ...` operator, ;;; and Excel has `if(..., ..., ...)` -- those are both the ;;; function-style `if`. An else-ish answer is REQUIRED. #| In Java: class Hand { public String toString( /* Hand this, */ ) { if (this.revealed) { return this.c1.toString() + "," + this.c2.toString(); } else { return "face-down"; } } } (Yeah, we could equally-well have made a *static* version of this method.) |# ; Here's a union type: ; A player-status is either: ; - a hand, or ; - an int (#minutes until next chance to play) ; Examples of the data: 3 (make-hand (make-card 'queen 'hearts) (make-card 4 'spades) false) ; a TEMPLATE for a function which handles a player-status. ; #; (define (handle-player-status ps) (cond [(hand? ps) ... (hand-c1 ps) ... (hand-c2 ps) ... (hand-revealed? ps)] [(integer? ps) ...])) ;;; Experience reveals that the following guideline ;;; is a good breaking-point: ;;; *** For a Union type, have a cond-branch per variant. ;;; If one particular variant is a union-type, then go ahead ;;; and pull out its fields (but not more). ; Consider the function: ; next-winning: player-status -> non-negative real? ; Return the expected winnings, for the next hand? (odds) ; Any compulsive gambler can tell you: ; - if good hand, 2.0; (A good hand, they'll say, is more than one bridge-points.) ; - if bad hand, 0.1; ; - if waiting: 1.9; ; a TEMPLATE for a function which handles a player-status. ; (check-expect (next-winning 17) 1.9) (check-expect (next-winning h1) 2.0) (check-expect (next-winning h2) 2.0) (check-expect (next-winning (make-hand 4s 4s false)) 0.1) (check-expect (next-winning (make-hand 4s (make-card 'jack 'spades) true)) 0.1) ; To help go from a blank-template to working-code, ; a helpful strategy is "inventory with values": ; take a specific test-case, and write in thought-bubbles for ; each expression in the template: ; what *value* does the expression have, for this specific input? ;; thought-bubbles:... ps: (make-hand 4s (make-card 'jack 'spades) true)) ;; (hand-c1 ps): (make-card 4 'spades) ;; (hand-c1 ps): (make-card 'jack 'spades) ;; (hand-revealed? ps) : false #;(define (handle-player-status ps) (cond [(hand? ps) ...(hand-c1 ps)...(hand-c2 ps)...(hand-revealed? ps)] [(integer? ps) ...])) ;; Instead, if given 17: ps: 17 (that's all) ; next-winning: player-status -> non-negative real? ; Return the expected winnings, for the next hand? (odds) ; #;(define (next-winning ps) (cond [(hand? ps) (if (> (+ (card->pts (hand-c1 ps)) (card->points (hand-c2 ps))) 1) 2.0 0.1)] [(integer? ps) 1.9])) ; Not bad, this but started to get cumbersome; can we break down the problem ; a bit differently, to keep it simple? ; next-winning: player-status -> non-negative real? ; Return the expected winnings, for the next hand? (odds) ; (define (next-winning ps) (cond [(hand? ps) (if (>= (hand->pts ps) 2) 2.0 0.1)] [(integer? ps) 1.9])) (check-expect (hand->pts h1) 2) (check-expect (hand->pts h2) 2) (check-expect (hand->pts (make-hand (make-card 'ace 'spades) (make-card 'king 'spades) false)) 7) ; hand->pts: hand? -> (and/c real? (not/c negative?)) ; Return a valuation (in bridge-points) of the hand. ; (define (hand->pts h) (+ (card->pts (hand-c1 h)) (card->pts (hand-c2 h)))) #| Follow the design recipe (the practically-final version!): ---- Per data type: 1. Choose data representation 2. Give some examples of the data 3. 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). c. ??? d. Inventory: In all cases, think about what *type* (each piece) is, and think of some functions that process that type. ---- Per function: 4. write test cases 5. (a-d): header, purpose, contract/type, ~stub-return~ copy the template, and stub it. 6. Inventory with values: Consider a particular test case, and think about what *specific* *value* of each sub-expression. 7. complete the body 8. run tests |# (check-expect (symbol->string1 'hello) "H") (check-expect (card->string (make-card 4 'spades)) "4S") (check-expect (card->string (make-card 'queen 'diamonds)) "QD")