;; 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 tail-recursion) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) ;; FYI: Scott ยง6.6.1 also discusses tail-recursion. #| ; Let's write the following function: ; string-append-whoa : list-of-strings -> string ; Return a string of all the elements in `whoa` appended, and ending in "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 ; Return a string of all the elements in `whoa` appended, and ending in "whoa". ; (define (string-append-whoa strs) (cond [(empty? strs) "whoa"] [(cons? strs) (string-append (first strs) " " (string-append-whoa (rest strs)))])) ; If we step through this with the stepper, ; we see that this template-derived version builds up many pending stack frames. (string-append-whoa (cons "hi" (cons "bye" (cons "aloha" '())))) ; Compare to what we'd do in Java: #| // 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,strs.size()); } 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") ; string-append-whoa2 : list-of-string -> string ; Return a string of all the elements in `whoa` appended, and ending in "whoa". ; (define (string-append-whoa2 strs) (saw2-help strs "whoa")) ; saw2-help : list-of-string, string -> string ; Return a string of all the elements in `strs` appended, and ending in `results-so-far`. ; (define (saw2-help strs result-so-far) ...) ; A function is 'tail recursive' if its recursive call is the last thing it ; needs to do. ; (It never needs to combine the *results* of the recursive call in any way.) ; We say "the recursive call is in tail position". ; An optimization: if a recursive call is in tail position, ; then don't allocate a new stack frame; just re-use the current one! ; This can turn a function which would use O(n) stack-space into O(1) stack space. ; To make our regular recursive function into a *tail recursive* one, ; we used the trick: ; add an "accumulator" variable (or, a "so-far" variable). ; Scheme (racket): compiler is *required* to implement tail-recursion optimization. ; 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)))])) #| /* Return the sum of all numbers in `nums`. */ double sum_v1( List nums ) { if (nums.isEmpty()) { return 0; } else { return nums.get(0) + sum_v1( nums.subList(1, nums.size()) ); } } /* Return the sum of all numbers in `nums`. */ double sum_v2( List nums ) { double sumSoFar = 0; while (!nums.empty()) { sumSoFar += nums.get(0); nums = nums.subList(1,nums.size()); } return sumSoFar; } |# |# ; Return the sum of all numbers in `nums`. (define (sum-v2 nums) (sum-v2-help nums 0)) ; Return the sum of all numbers in `nums` plus sum-so-far. (define (sum-v2-help nums sum-so-far) (cond [(empty? nums) sum-so-far] [(cons? nums) (sum-v2-help (rest nums) (+ (first nums) sum-so-far))])) (sum-v2 (cons 7 (cons 99 (cons 4 '())))) #;(check-expect (sum-v2-help (cons 7 (cons 2 '())) 99) 108) ; 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 : 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) ...) #| The corresponding Java code for sum-v2 and the loop sum-help: double sum_v2( List nums ) { while (!nums.isEmpty()) { } // 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) ;;;;;;;;;; ;; To think about: ; Racket doesn't have a 'while' loop built in (at least, the student-languages don't). ; But we've just seen that tail-recursion is, in its essence, a loop. ; So can we write our own while-loop in racket, *as a [tail-recursive] function* ?!? ; How would we call it? ; Imagine what a while-loop might look like, in racket. ; Let's consider a version which, each time it loops, ; gathers the "body" expression into a list. ; For example, we could call: #| ; COMMENTED OUT BECAUSE IT'S AN ERROR IN BEGINNING-STUDENT ; TO MENTION A FUNCTION W/O CALLING IT (like `sub1` or `positive?`). ; We'll move to intermediate-with-lambda!! (while/list 7 positive? sub1 identity) ; "starting w/ i=7, and while (positive? i), collect i into a list and then i := (sub1 i)" "expect: " (list 7 6 5 4 3 2 1) (while/list 17 positive? sub1 sqrt) ; "starting w/ i=7, and while (positive? i), collect (sqrt i) into a list and then i := (sub1 i)" "expect: " (list 4.101 4 3.98 ... 1.414 1) ; Coming up -- writing `while/list` ! (define (while/list i continue? end-of-loop-update body) "hmmm") ; Furthermore, we want our `while/list` to be tail-recursive ; (so that it counts as a true loop, morally). |#