;; 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-intermediate-lambda-reader.ss" "lang")((modname struct-intro-book-after) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) ;;; structs in racket ;;; i.e. aggregate data: take several pieces of data, ;;; bundle them up together and consider it *one* thing ;;; (understanding it is comprised of several internal fields). ;;; "encapsulation" -- we put a capsule around the fields. (require "student-extras.rkt") ; for define-struct/contract ;; Task: ;; To-model: a book has ;; a title, AND author, AND # of pages, AND a copyright status. ; Datatype Definiton: ; without contracts: ;(define-struct book (title author num-pages is-copyrighted?)) ; Datatype Definiton: (define-struct/c book ([title string?] [author string?] [num-pages natural?] [is-copyrighted? boolean?])) ; Examples of the data (make-book "The Cat in the Hat" "Dr. Seuss" (+ 2 35) #true) (make-book "The Soul of Wit" "Barland" 0 #false) ; In this case, we'll want to use these examples for test-cases later, ; so let's bind them to identifiers: (define cih (make-book "The Cat in the Hat" "Dr. Seuss" (+ 2 35) #true)) (define b0 (make-book "The Soul of Wit" "Barland" 0 #false)) ; `define-struct` actually introduces additional functions: ; a constructor ; and four getters ("selectors"). ; ; In this case, the constructor is named ; make-book : (-> string? string? natural? boolean? book?) ; and the getters are named ; book-title : (-> book? string?) ; book-author ; book-num-pages : (-> book? natural?) ; book-is-copyrighted? : (-> book? boolean?) ; and a predicate ; book? : ANY -> boolean ; Examples of calling getters: (book-title (make-book "The Cat in the Hat" "Seuss" 37 #true)) (book-is-copyrighted? b0) ; This is just calling-a-function; don't confuse it w/ Java-esque attempts like: ; b0.is-copyrighted? (is-copyrighted? b0) is-copyrighted? book-is-copyrighted? (which is not *calling* the func) ; (I see the latter two a lot, on hand-written exams.) ;;; Hey, wait a minute, I'm sensing a pattern [after having written several book functions]: ;;; EVERY book-handling function has some stuff in common -- namely, extracting its 4 fields. ;;; Let me write *that* much code just once, so I can copy-paste! ;;; We'll call that "the template" for `book`s. ;;; template ;;; [that is, what *any* book-handling function will need to do: ;;; select fields, and then somehow combine those (the `(... )`)] ; (define/contract (func-for-book a-book) (-> book? ...?) ; Returns …[purpose-statement here] (... (book-title a-book) (book-author a-book) (book-num-pages a-book) (book-is-copyrighted? a-book))) ;;; Design Recipe Step 3: Write the template for your data type. ;;; 3a. for a struct with k fields, just call the k getters ;;; Now let's write a function: `reading-time`: ; Suppose it takes the avg person 2min/page. (check-expect (reading-time (make-book "The Cat in the Hat" "Seuss" 37 #true)) 74) (check-expect (reading-time b0) 0) (define/contract (reading-time a-book) (-> book? non-negative-real?) ; How long it takes the average reader to read a book (in minutes) (* (book-num-pages a-book) 2)) ; Compare to java: ; class Book{ … ; int readingTime( /* Book this*/ ) { return 2*this.num-pages; } ; } ;;; Another function: `book->string`: (check-expect (book->string (make-book "The Cat in the Hat" "Seuss" 37 #true)) "The Cat in the Hat, by Seuss (37pp), ©") (check-expect (book->string b0) "The Soul of Wit, by Barland (0pp)") (define/contract (book->string a-book) (-> book? string?) ; Return a user-friendly string-representation of a book. (string-append (book-title a-book) ", by " (book-author a-book) " (" (number->string (book-num-pages a-book)) "pp)" (if (book-is-copyrighted? a-book) ", ©" ""))) ;;; Another function. (check-expect (enbiggen b0) b0) (check-expect (enbiggen cih) (make-book "The Cat in the Hat" "Dr. Seuss" 111 #true)) (define/contract (enbiggen a-book) (-> book? book?) ; Return a book just like `a-book`, but with bigger text (hence, more pages). (make-book (book-title a-book) (book-author a-book) (* 3 (book-num-pages a-book)) (book-is-copyrighted? a-book))) ;;; ***OBSERVE: we don't change/re-assign/mutate a field; ;;; instead we are creating a new object, and copying over the unchanged fields. ;;; This style of immutable data is different from what you might be used to from Java. ;;; (Though note that java `String`s work exactly like this, and nobody really minds that.) ;;; Yet another function, `derive-from`: (check-expect (derive-from cih "Baralnd" 10) (make-book "Son of The Cat in the Hat" "Dr. Seuss & Baralnd" 47 #false)) (check-expect (derive-from b0 "Banksie" 0) (make-book "Son of The Soul of Wit" "Barland & Banksie" 0 #false)) (check-expect (derive-from (make-book "The Last Airbender" "Ang" 230 #true) "Korra" 170) (make-book "Son of The Last Airbender" "Ang & Korra" 400 #false)) (define/contract (derive-from original new-author num-new-pages) (-> book? string? natural? book?) ; Create a brand new book, based on an original, ; modified by `new-author`, who added `num-new-pages` to `original`. ; (make-book (string-append "Son of " (book-title original)) (string-append (book-author original) " & " new-author) (+ (book-num-pages original) num-new-pages) #false))