;; 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 my-max) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) (require "let-for-bsl.rkt") ; see below for how to use/install this file yourself. #| Follow the design recipe (the final version!): ---- Per data type: 1. Choose datatype definition 2. Give some examples of the data 3. Template: 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. d. Add the natural recursive call (as appropriate) ---- Per function: 4. write test cases 5. (a-d): copy the template; complete header, purpose, contract/type; stub the "..."s. 6. Inventory with values: Consider a particular test case, and think about what *specific* *value* of each sub-expression template has. 7. complete the body 8. run tests |# ; Datatype Definition: #;(define (list? val) (or (equal? val '()) ; interpretation: an empty list (cons? val))) #;(define-struct/c cons ([first any/c] [rest list?])) ; interpretation: `first` tacked to the front of the list `rest`. ; leave this commented-out, since it's already built-in to racket ; '() is the empty list; there is also the named-constant `empty`, bound to it. ; Note that we could use any value we like, to represent the empty-list -- just so long ; as we can distinguish it from a cons-struct. ; Examples of data: '() (cons 17 '()) (cons 15 (cons 17 '())) (cons 13 (cons 15 (cons 17 '()))) (first (cons 13 (cons 15 (cons 17 '())))) (rest (cons 13 (cons 15 (cons 17 '())))) ; The constructor isn't *actually* `make-cons`, but instead just `cons`. ; The selectors aren't *actually* `cons-first`, but instead just `first` ; and not `cons-rest`, but instead just `rest`. ; ; There *is* a predicate `cons? : (-> any/c boolean?)`. ; Also, there is a predicate to test for the empty-list: `empty? : (-> any/c boolean?)` ;; template, for *any* function handling a list: ; (define/contract (func-for-list a-lon) (-> list? ...?) ; Return ... of `a-lon`. (cond [(equal? a-lon '()) ...] [(cons? a-lon) (... (first a-lon) (func-for-list (rest a-lon)))])) ; inventory with values: suppose that `a-lon` stands for ; (cons 5 (cons 4 (cons 3 '()))). ; So what is (first a-lon) in this case? _____ ; So what is (rest a-lon) in this case? ___________________ ; So what is (func-for-list (rest a-lon)) in this case? ; __[read answer from your test-cases]__ ; So what should my answer be in this case? __[read answer from your test-cases]__ ;;;;;;;;;;;;;;; my-max ;(check-expect (my-max '()) ...) (check-expect (my-max (cons 7 '())) 7) (check-expect (my-max (cons 7 (cons 7 '()))) 7) (check-expect (my-max (cons 4 (cons 7 '()))) 7) (check-expect (my-max (cons 4 (cons 7 '()))) 7) (check-expect (my-max (cons -5 (cons -4 (cons -3 (cons -2 (cons -1 '())))))) -1) ; suppose a-lon were: (cons 7 (cons 9 (cons 2 '()))) ; then (first a-lon) would be: 7 ; and (my-max (rest a-lon)) would be: 9 ; and the answer I need to give back is: 9 ; my-max : list-of-number -> number ; Return the largest element of `a-lon`, or ____ if it's empty. ; (define/contract (func-for-list a-lon) (-> list? ...?) ; Return ... of `a-lon`. (cond [(equal? a-lon '()) ...] [(cons? a-lon) (... (first a-lon) (func-for-list (rest a-lon)))])) ; This passes all our unit-tests! (but see below) ; Some points: ; The +inf.0 trick doesn't work for (say) min-string. ; Really, the contract is wrong: should be non-empty-list. ; (To think about: what is Datatype def'n, for non-empty-list?) ; Later: We'll see a sol'n: a helper-function, w/ an accumulator: ; `max-helper : number, list-of-number -> number` ; This passes all our unit-tests! ; However, this implementation does have a problem, ; only revealed under stress tests: ; #;(check-expect (my-max (cons -18 (cons -17 (cons -16 (cons -15 (cons -14 (cons -13 (cons -12 (cons -11 (cons -10 (cons -9 (cons -8 (cons -7 (cons -6 (cons -5 (cons -4 (cons -3 (cons -2 (cons -1 '()))))))))))))))))))) -1) #;(check-expect (my-max (cons -1 (cons -1 (cons -1 (cons -1 (cons -1 ; todo: change the above -1's to -20, -21, etc. (cons -19 (cons -18 (cons -17 (cons -16 (cons -15 (cons -14 (cons -13 (cons -12 (cons -11 (cons -10 (cons -9 (cons -8 (cons -7 (cons -6 (cons -5 (cons -4 (cons -3 (cons -2 (cons -1 '()))))))))))))))))))))))))) -1) ; The problem is the repeated computation: ; every time the result-of-recursive call is bigger than the first, ; we *re*calculate the recursive call. ; (Worst case is ascending list, where the recursive result is always bigger). ; Solution: use a variable, to remember the result of the recursive call: ; Use `let*` ; Example of using `let*`: #;(let* {[a 17] [b 23] [c (sqrt (+ 99 b))]} (+ a (cos c))) #;(define (my-max a-lon) (cond [(empty? a-lon) -inf.0] [(cons? a-lon) (let {[max-of-rest (my-max (rest a-lon))] } (if (< (first a-lon) max-of-rest) max-of-rest (first a-lon)))])) #;(my-max (cons 3 (cons 7 (cons 2 (cons 9 '()))))) ; Note: we will soon RE-visit `let*`, to discuss scope. But first: #| Syntax for let* -> (let* () ) -> ϵ | -> [ ] Semantics of let: An expression whose value is: the value of the (last, "body") expression, except that we substitute any of the introduced variables with their right-hand-side. (This is, like 90% true ... read about `let` vs `let*` for the whole story.) |# ; It's worth noting that we can solve the "exponential behavior of `my-max`" ; without introducing `let`; some students already wondered why not use ; a helper: ; max-of-two : num, num -> num ; Return the larger of `a`, `b`. ; (define (max-of-two a b) (if (< a b) b a)) (check-expect (max-of-two 4 7) 7) (check-expect (max-of-two 7 4) 7) (check-expect (max-of-two 7 7) 7) (check-expect (max-of-two -2 -3) -2) #| == Want to use `let` or `let*` in your programs? == It will be present when we shift up to intermediate-student. However, if you can't wait that long, I've arranged a workaround: 1. put the file let-for-bsl.rkt into the same directory as your program - get the file from the same folder as this file (see the URL) perhaps: http://www.radford.edu/itec380/2018fall-ibarland/Lectures/let-for-bsl.rkt - either download the file directly, or copy/paste into a blank file. BUT if you copy/paste into an open DrRacket window, you'll need to make sure the file isn't accidentally made into the beginning-student language: choose Language » Choose Language… » Full Racket Language and save the file. You should be able to run the file from within DrRacket and have no errors (and be able to then use `let` right in the interactions-pane!). 2. in your own program, include the line (require "let-for-bsl.rkt") 3. Profit. |# ; Btw, that's all the racket I'll teach: ; - call a function ; - `define` for functions ; - `define` for flat values ; - `cond` ; - `define-struct` ; - [ okay, we'll introduce one more: function-literals, which will collapse our two `define`s ] ; ; Certainly racket has other things (a class-system, macros, a module system, ...) ; but we're not teaching racket; we're teaching a few fundamental concepts ; (and a language where we don't need to bring in extra concepts to just use the basics). ; ; Note that technically, `if` `and` `or` aren't functions ; (since they short-cut!); but I'll sweep that under the rug ; and pretend they're just library-functions and not separate syntax. ; The Law of Scheme (and, Racket): ; An Expr is: ; ... ; (eval an-expr) is: ; cond ; ...lambda, so that 'define' meets the above ; ...'let' as 'lambda'. #| ; First: `let*`: use a variable(?) to avoid repeated computation. ; (Hey, could we do this with a helper function instead of `let*`?) ; (Does that trick ALWAYS work?) ; We can convert the following `let`... #;(let {[a 17] [b 99]} (if (> a 20) (sqrt (+ a b)) (expt a b))) ;; into the following `let`-free version: (define (conversion-helper a b) (if (> a 20) (sqrt (+ a b)) (expt a b))) #;(conversion-helper 17 99) ;; And if we want to avoid a top-level define, ;; we can use an anonymous function (see ;; upcoming lecture on functions-as-values): #;((lambda (a b) (if (> a 20) (sqrt (+ a b)) (expt a b))) 17 99) |# #| ; - another data-def'n: natnum ; - ! ; - nums-up-to-zero ; Return a list of `i` numbers leading up to 0 (e.g. -3,-2,-1). ; (define (nums-below-0 i) (cond [(zero? i) '()] [(positive? i) (cons (- i) (nums-below-0 (sub1 i)))])) (check-expect (nums-below-0 2) (cons -2 (cons -1 '()))) (check-expect (nums-below-0 0) '()) (check-expect (nums-below-0 1) (cons -1 '())) |# #| (define (first/safe lst dflt) (cond [(empty? lst) dflt] [else (first lst)])) ; w/ an add'l argument: ; deltas; cumulate (check-expect (cumulate-right '()) '()) (check-expect (cumulate-right (cons 4 '())) (cons 4 '())) (check-expect (cumulate-right (cons 3 (cons 4 '()))) (cons 7 (cons 4 '()))) (check-expect (cumulate-right (cons 2 (cons 3 (cons 4 '())))) (cons 9 (cons 7 (cons 4 '())))) (define (cumulate-right nums) (cond [(empty? nums) '()] [(cons? nums) (cons (+ (first nums) (first/safe (cumulate-right (rest nums)) 0)) (cumulate-right (rest nums)))])) #| (define (cumulate nums) '()) (check-expect (cumulate '()) '()) (check-expect (cumulate (cons 4 '())) (cons 4 '())) (check-expect (cumulate (cons 3 (cons 4 '()))) (cons 3 (cons 7 '()))) (check-expect (cumulate (cons 2 (cons 3 (cons 4 '())))) (cons 2 (cons 5 (cons 9 '())))) |# |# ; `lambda`