;; 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 lect23-tail-recursion) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ()))) ;; FYI: Scott §6.6.1 also discusses tail-recursion. ; Let's write the following function `string-append-whoa`: ; #| (check-expect (string-append-whoa '()) "whoa") (check-expect (string-append-whoa (cons "aloha" '())) "aloha whoa") (check-expect (string-append-whoa (cons "bye" (cons "aloha" '()))) "bye aloha whoa") (check-expect (string-append-whoa (cons "hi" (cons "bye" (cons "aloha" '())))) "hi bye aloha whoa") |# ; string-append-whoa : list-of-strings -> string ; (define (string-append-whoa strs) (cond [(empty? strs) "whoa"] [(cons? strs) (string-append (first strs) " " (string-append-whoa (rest strs)))])) ;(string-append-whoa (list "hi" "bye" "aloha")) ; Here's an equivalent 'spelled out' version which uses the template: ; HOWEVER, we are now realizing that it builds up many stack frames ; (as demo'd in the stepper): #| // Java loop version of the above: // String stringAppendWhoa( List strs ) { String resultSoFar = "whoa"; while (!strs.isEmpty()) { resultSoFar = strs.get(0) + " " + resultSoFar; strs = strs.sublist(1); } return resultSoFar; } // Tracing through -- at the start of each iteration: // strs resultSoFar // ---------- ----------- // {"hi","bye","aloha"} "whoa" // {"bye","aloha"} "hi whoa" // {"aloha"} "bye hi whoa" // {} "aloha bye hi whoa" // Why doesn't this loop build up stack frames? // Because the `while` turns into // a "goto start of loop, with revised `resultSoFar` and `strs`." |# ; If we also use a 'so-far' variable, then we can ; write a racket version which doesn't build up stack-frames: (check-expect (string-append-whoa2 (list "hi" "bye" "aloha")) "hi bye aloha whoa") (define (string-append-whoa2 strs) (saw2-help strs "whoa")) (define (saw2-help strs result-so-far) (cond [(empty? strs) result-so-far] [(cons? strs) (saw2-help (rest strs) (string-append (first strs) " " result-so-far))])) ; To achieve tail-recursion, we used the trick: ; add an "accumulator" variable (or, a "so-far" variable). ; A function is 'tail recursive' if its recursive call is the last thing it ; needs to do. ; (It doesn't need to combine the *results* of the recursive call in any way.) ; If a language implements tail-recursion, it won't build up stack frames ; for TAIL-recursive calls. ; Scheme (racket): compiler is *required* to implement tail-recursion. ; Java : cannot implement tail recursion (the jvm spec doesn't allow it, and ; Java must be backwards-compatible). The reason is ??: to preserve ; stack-traces in the presence of exceptions (which might get thrown ; anywhere)? ;;;;; One more example: summing number in a list: ; sum_v1 : (listof number) -> number ; Return the sum of all numbers in `nums`. ; This v1 follows the template -- is *not* tail-recursive. ; (define (sum-v1 nums) (cond [(empty? nums) 0] [(cons? nums) (+ (first nums) (sum-v1 (rest nums)))])) ; sum-v2 : (listof number) -> number ; ; Return the sum of all numbers in `nums`. ; This v2 follows adds an accumulator -- *is* tail-recursive. ; (define (sum-v2 nums) (sum-help 0 nums)) ; sum-help : number, (listof number) -> number ; Return the sum of all the numbers in `nums`, added to `sum-so-far`. ; See also: the loop inside sum_v2 in Java. ; (define (sum-help sum-so-far nums) (cond [(empty? nums) sum-so-far] [(cons? nums) (sum-help (+ (first nums) sum-so-far) (rest nums))])) #| The corresponding Java code for sum-v2 and the loop sum-help: double sum_v2( List nums ) { double sumSoFar = 0.0; while (!nums.isEmpty()) { // Loop invariant: sumSoFar + sum of numbers in `nums` is same, // each time through the loop. sumSoFar = nums.get(0) + sumSoFar; // Cf. "sumSofar = (first nums) + sumSoFar" nums = nums.sublist(1); // Cf. "nums = (rest nums)" } // Reach here when nums.isEmpty: return sumSoFar; } |# (check-expect (sum-v1 '()) 0) (check-expect (sum-v1 (list 2)) 2) (check-expect (sum-v1 (list 2 5)) 7) (check-expect (sum-v1 (list 2 5 -3 1)) 5) (check-expect (sum-v2 '()) 0) (check-expect (sum-v2 (list 2)) 2) (check-expect (sum-v2 (list 2 5)) 7) (check-expect (sum-v2 (list 2 5 -3 1)) 5) ;;;;;;;;;; ;; Loops in scheme: ; There isn't a 'while' loop built in. ; Can we write our own, as a function? ; How would we call it? (check-expect (nums-down-from 7) (list 7 6 5 4 3 2 1)) ;(sqrts-down-from 17) = (list 4.101 4 3.98 ... 1.414 1) (define (nums-down-from n) (while/list n positive? sub1)) (check-expect (while/list (list "a" "b" "c") cons? rest) (list (list "a" "b" "c") (list "b" "c") (list "c"))) ; while/list: α, (α -> boolean), (α -> α) -> (list-of α) ; Return a list containing `indx`, `(update indx)`, `(update (update indx))`, ... ; so long as `continue?` of that quantity returns true. ; (define (while/list indx continue? update) (reverse (wl-help indx continue? update '()))) ; wl-help : α, (α -> boolean), (α -> α), (list-of α) -> (list-of α) ; Return a list containing `indx`, `(update indx)`, `(update (update indx))`, ... ; so long as `continue?` of that quantity returns true -- ; all prepended to the front of `results-so-far`. ; (define (wl-help indx continue? update result-so-far) (cond [(continue? indx) (wl-help (update indx) continue? update (cons indx result-so-far))] [else result-so-far])) ; Q1: Call while/list to count from 10 to (but not including) 50, by 7s. (while/list 10 (λ (k) (< k 50)) (λ (n) (+ n 7))) ; ; Q2: Write the function 'nums-from-to', ; whose body is just a single call to `while/list`. ; ; Q3: Write `for/list`, which takes a start, stop, and body ; and returns a list of all the body-results. ; i. Implement this w/o calling any of the functions above ; ii. What's the cheapo way of writing this, using functions above? ;;;; (require 2htdp/image) (while/list empty-image (λ (img) (< (image-width img) 150)) (λ (img) (let* {[r (image-width img)]} (overlay img (square (+ r 9) 'solid (if (even? r) 'blue 'red))))))