;; 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 lect05b) (read-case-sensitive #t) (teachpacks ((lib "universe.ss" "teachpack" "2htdp") (lib "image.ss" "teachpack" "2htdp"))) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ((lib "universe.ss" "teachpack" "2htdp") (lib "image.ss" "teachpack" "2htdp"))))) ; we saw: ; list ; lambda ; let -- can be re-written as lambda ; (the my-min where we called min-of-two as a helper, ; suddenly we didn't need the let) ; (define phils-number 99) (define (root1 a b c) (+ a 17)) ; becomes: (define root1-v2 (lambda (a b c) (+ a 17))) (root1-v2 2 (* 2 3) phils-number) = ( (lambda (a b c) (+ a 17)) 2 6 99) = (+ 2 17) = 19 ; Exercise: re-write a `let` as a `lambda`. ; (this also motivates `let` vs `let*`. ; Observe how stepping through my-min, we build up pending ; operations. Similar, the function to add all the numbers in the ; list builds up pending additions. ; Def'n: tail-recursion: ; A function is tail-recursive if the result of the recursive call ; gives exactly the result for the original function call, ; without any further processing. ; ; This is a good feature because it means a smart implementation does ; *not* need to build up all the pending evaluations. ; In machine architecture, this corresponds to not needing to allocate ; a new stack frame on each recursive call -- just update the ; parameters and do an assembly-code JMP back up to the start of the function. ; ; Hey, that's how loops are implemented! ; UPSHOT: Loops are a special, constrained form of recursion (tail-recursion). ; If you have a language w/ recursion but not loops, you don't worry, ; because you can just use tail-recursion. ; However, the reverse is not easy: If you have a language with ; loops but no recursion, the only way to get recursion is to ; (essentially) keep track of the call-stack yourself. ; [This clearly *can* be done: early assembly-languages didn't have a ; "JSR" (jump-to-subroutine) instruction, and yet they could implement ; Lisp in 1965.] ; ; The scheme language standard *requires* that tail-recursive functions ; be safe-for-space [essentially, that they turn tail-recursion into loops ; at the assembly language level]. ; ; (Technical aside: ; more properly, we should say ; "A function's *body* is tail-recursive if ; all its recursive calls are in tail-position". ; "body" because we are talking about a static, syntactic property ; that depends only on the program source-text. ; Since "...without any further work" is a *run*-time concept, ; it's not quite technically correct. ; However, it's good enough for this class; it captures the essence of ; "a call to this function can be evaluated w/o building up stack frames", ; a property also known as "safe for space" (that is, a function ; that can be solved in O(1) memory won't require O(n) memory).) ; SO: ; Let's convert a non-tail-recursive function to tail-recursion: ; sum-v1 and sum-v2, in Java. ; ; sum-v2, in scheme ; Exercise: write a tail-recursive version of my-min. (define (sum-v2 data sum-so-far) (cond [(empty? data) sum-so-far] [(cons? data) (sum-v2 (rest data) (+ sum-so-far (first data)))])) (check-expect (sum-v2 (list 5 2 4 1) 0) 12) (define-struct foo (a b)) (check-within (make-foo 2.1 2.2) (make-foo 2.2 #e2.3) 0.8) (define (sum-v1 data sum-so-far) (cond [(empty? data) 0] [(cons? data) (+ (first data) (sum-v2 (rest data)))]))