;; 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 start-design-recipe-after) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) #| `define`: a keyword to bind an identifier to a value: |# (require "student-extras.rkt") ; for `natural?`, `non-negative-natural?`, etc. ; download this file, linked from the bottom of the lectures page. (define n 37) (define m (* n 99)) (define ϕ (/ (+ 1 (sqrt 5)) 2)) (define fname "Ian") (define lname "B-meister") ; Syntax for define-a-variable: ; (define ) ; Syntax for define-a-function: ;(define ( …) ; ) #| The Design Recipe (take 1 -- primitive types only) ------- per function: 4. tests (consider: 0,1,many; strings/lists/arrays/collections of length 0,1,many; fractions & negatives 5. SSS: signature, purpose-statement, and stub (purpose-statement should mention each arg by name; always mention units if there are any) 7. complete the body-expression 8. watch your tests pass |# ;;;;;;;;;;;; Example, of design recipe ;;;;;;;;;;;;;;;;;;;;; (check-within (pizza-area 18) 254.5 0.05) ; Make a few more tests! (check-expect (pizza-area 0) 0) (check-within (pizza-area 2) 3.14159 0.00001) (check-within (pizza-area 1) (/ pi 4) 0.000001) (check-within (pizza-area 0.5) (* pi 0.25 0.25) 0.00001) ; pizza-area must be given a non-negative number ; `check-expect` and `check-within` are part of the language. ; Test-cases include an actual-call (with specific inputs), ; and a specific expected-result. They are *runnable code*! ; ; (comparable to junit's `AssertEquals(Object,Object)` ; and `AssertEquals(double,double,double)`). ; The area (in sq.in.) of a pizza, whose diameter is `diam` (in inches) ; (define/contract (pizza-area diam) (-> non-negative-real? non-negative-real?) (* pi (sqr (/ diam 2)))) ; New syntax: `define` for functions. ; Note how it mirrors how the function is called (but params instead of argument-expressions). ; Syntax for define-a-function: ; #| TODO in class |# ;;; EXAMPLE: Together, we'll write: `monogram`: ; Given a first name and last name, return a monogram with initials: (check-expect (monogram "Ian" "Barland") "i.b.") (check-expect (monogram "Jay" "Z") "j.z.") (check-expect (monogram "Tracy" "Lewis-Williams") "t.l.") (check-expect (monogram "50¢" "!&*^") "5.!.") ; Given a first name `f-name` and last name `l-name`, return a monogram with initials: #;(define (monogram f-name l-name) ;(-> non-empty-string? non-empty-string? string?) (string-append (string-downcase (substring f-name 0 1)) "." (string-downcase (substring l-name 0 1)) ; repeated code! ".")) ;; We've repeated the 'substring', 'string-downcase', and appending-to-"." twice! ;; Should our boss ever change our requirements, we'll have to apply our changes to ;; *both* parts of the code (and if we forget, we'll have a bug). ;; Poor design, not having a single point-of-control. ;; ;; Now, let's re-factor `monogram`, to get rid of the repeated code! #| done in class: |# ; Given a first name `f-name` and last name `l-name`, return a monogram with initials: (define (monogram f-name l-name) ;(-> non-empty-string? non-empty-string? string?) (string-append (initialize f-name) (initialize l-name))) ;;; EXAMPLE: Okay, now YOU write: `initialize`: ; Return the first letter of a word downcase'd, followed by ".". (check-expect (initialize "Ian") "i.") (check-expect (initialize "e.e.cummings") "e.") ; Return the initial" for `wrd` (first letter downcased, followed by '.') (define/contract (initialize wrd) (-> non-empty-string? non-empty-string?) (string-append (string-downcase (substring wrd 0 1)) ".")) #| One reaction to factoring out `initialize` above is: "Gee Barland, you did a bunch of extra work, for a fairly small amount of duplicate-code-elimination." (a) Duplicate code smells bad; (b) Smaller functions leads to better testing-via-unit-tests; it reduces the need for printf-debugging or stepping with a debugger. (c) The helper-function might even be useful in its own right. |# ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;; Bonus full-racket code-teaser ;;;;;;;;;;;;;;;;;;;;;;;;; #| As an example of (c) above, AND a preview of the higher-order function `map` that we'll discuss later, here's another function. (You are NOT expected to be able to sit down and write this ...yet!) The following code uses the full-racket language; copy/paste the contents of the #|...|# below into a new DrRacket window, and select: Languages > Choose Language... > The Racket Language ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |# ; VERSION 1: (check-expect (acronymize "Self contained underwater breathing apparatus") "s.c.u.b.a.") ; (use intermediate-student approach, after we learn about `map`) ; Given a multi-word phrase, return its acronym. ; pre-condition: no "empty words" in phrase, caused by multiple spaces in a row. ; (define/contract (acronymize phrase) (-> string? string?) (apply string-append (map initialize (string-split phrase)))) (check-expect (acronymize "") "") (check-expect (acronymize "born") "b.") (check-expect (acronymize "Self contained underwater breathing apparatus") "s.c.u.b.a.") ;;; Again, to be clear: ;;; In this class we will NOT use full-racket, nor worry about regexp's etc. ;;; (We will, though, use `map` in intermediate-student, in a few weeks.) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; VERSION 2 -- filter out common words (which are an optional-arg, with common defaults): ; (check-expect (acronymize-v2 "The light-amplified stimulated emission of radiation") "l.a.s.e.r.") (check-expect (acronymize-v2 "The good ol' United States o' America" '("the" "o'")) "g.o.u.s.a.") ; ; (use full-racket approach, using `filter` and `λ` (that we will cover in a few weeks), ; as well as things we will NOT cover in this course, but are part of full-racket: ; optional-arguments (and their contract), `apply`, `#px` for regular-expressions. ; The (require (only-in racket/contract ->*)) ; grab full racket's contracts for optional-arguments, ->* (define COMMON-WORDS (list "the" "and" "of" "or" "and" "")) ; Return an acroynum for the given `phrase`, not including `words-to-ignore`. ; Hyphenated-words get all sub-words included. ; pre-condition: no "empty words" in phrase, caused by multiple spaces-or-dashes in a row. ; (define/contract (acronymize-v2 phrase [words-to-ignore COMMON-WORDS]) (->* (string?) [(listof string?)] string?) (apply string-append (map initialize (filter (λ(wrd) (not (member wrd words-to-ignore))) (string-split (string-downcase phrase) #px"[ -]"))))) ; Note that this function has an optional-argument `words-to-ignore` ; (which affects the contract). (check-expect (acronymize-v2 "born") "b.") (check-expect (acronymize-v2 "") "") (check-expect (acronymize-v2 "the the" ) "") (check-expect (acronymize-v2 "the-the" ) "") (check-expect (acronymize-v2 "the of-the of") "") (check-expect (acronymize-v2 " funky string w/ extra spaces!? ") "f.s.w.e.s.") ; This caught an unexpected bug! ; I kludged this bug by adding "" to COMMON-WORDS. ; (But if a user-provided `words-to-ignore` doesn't include "", the bug is still there! ; And admittedly, "" isn't actually a “common word”.) ; A *real* solution is to replace `(member str words-to-ignore)` with `(member str (cons "" words-to-ignore)` -- ; but i want to keep that complexity out of the solution, since there's already enough stuff ; happening for a beginner to get a flavor of full-racket! (check-expect (acronymize-v2 "Self contained underwater breathing apparatus") "s.c.u.b.a.") (check-expect (acronymize-v2 "The light-amplified stimulated emission of radiation") "l.a.s.e.r.") (check-expect (acronymize-v2 "The good ol' United States o' America" '("the" "o'")) "g.o.u.s.a.") ;;; Again, to be clear: ;;; In this class we will NOT use full-racket (regexps nor optional-args nor `apply`). ;;; (We will, though, use `map` in a few weeks.)