overview
play

Overview What is itera*on? Racket has no loops, and yet can express - PowerPoint PPT Presentation

Overview What is itera*on? Racket has no loops, and yet can express itera*on. Iteration via Tail Recursion in Racket How can that be? - Tail recursion! Tail recursive list processing via foldl Other useful abstrac*ons - General


  1. Overview • What is itera*on? • Racket has no loops, and yet can express itera*on. Iteration via Tail Recursion in Racket How can that be? - Tail recursion! • Tail recursive list processing via foldl • Other useful abstrac*ons - General itera*on via iterate and iterate-apply CS251 Programming Languages - General itera*on via genlist and genlist-apply Spring 2019, Lyn Turbak Department of Computer Science Wellesley College Iteration/Tail Recursion 2 An itera*ve approach to factorial Factorial Revisited Invoca'on Tree State Variables: Idea: multiply (fact-rec 4): 24 (define (fact-rec n) on way down • num is the current number being processed. (if (= n 0) pending multiplication is nontrivial glue step -1 4 1 1 * • prod is the product of all numbers already processed. (* n (fact-rec (- n 1))))) glue divide (fact-rec 3): 6 -1 * Itera'on Table: Small-Step Seman'cs -1 * step 3 4 num prod 1 4 1 ({fact-rec} 4) (fact-rec 2): 2 -1 * 2 3 4 � {(λ_fact-rec 4)} divide 3 2 12 � * (* 4 {(λ_fact-rec 3)}) -1 * 2 12 4 1 24 � * (* 4 (* 3 {(λ_fact-rec 2)})) (fact-rec 1): 1 � * (* 4 (* 3 (* 2 {(λ_fact-rec 1)}))) 5 0 24 -1 * � * (* 4 (* 3 (* 2 (* 1 {(λ_fact-rec 0)})))) -1 * � * (* 4 (* 3 (* 2 {(* 1 1)}))) 1 24 Itera'on Rules: � (* 4 (* 3 {(* 2 1)})) (fact-rec 0): 1 • next num is previous num minus 1. � (* 4 {(* 3 2)}) -1 * � {(* 4 6)} • next prod is previous num 'mes previous prod . � 24 24 0 Iteration/Tail Recursion 3 Iteration/Tail Recursion 4

  2. Tail-recursive factorial: Dynamic execu*on Itera*ve factorial: tail recursive version in Racket Invoca'on Tree State Variables: (define (fact-iter n) • num is the current number being processed. (fact-tail n 1)) (fact-iter 4) • prod is the product of all numbers already processed. (define (fact-tail num prod) (fact-tail 4 1) (define (fact-tail num prod ) (if (= num 0) divide (if (= num 0) prod (fact-tail 3 4) (fact-tail (- num 1) (* num prod)))) prod stopping (fact-tail 2 12) (fact-tail (- num 1) (* num prod)))) condition Small-Step Seman'cs (fact-tail 1 24) tail call (no pending operations) expresses iteration rules ({fact-iter} 4) Itera'on Rules: � {(λ_fact-iter 4)} (fact-tail 0 24) Itera'on Table • next num is previous num minus 1. � {(λ_fact-tail 4 1)} no glue! � * {(λ_fact-tail 3 4)} • next prod is previous num 'mes previous prod . step num prod � * {(λ_fact-tail 2 12)} 1 4 1 ;; Here, and in many tail recursions, need a wrapper � * {(λ_fact-tail 1 24)} 2 3 4 ;; function to initialize first row of iteration � * {(λ_fact-tail 0 24)} 3 2 12 ;; table. E.g., invoke (fact-iter 4) to calculate 4! � * 24 4 1 24 (define (fact-iter n) 5 0 24 (fact-tail n 1)) Iteration/Tail Recursion 5 Iteration/Tail Recursion 6 inc-rec in Racket The essence of itera*on in Racket ; Extremely silly and inefficient recursive incrementing • A process is itera*ve if it can be expressed as a sequence of steps ; function for testing Racket stack memory limits that is repeated un*l some stopping condi*on is reached. (define (inc-rec n) (if (= n 0) • In divide/conquer/glue methodology, an itera*ve process is a 1 recursive process with a single subproblem and no glue step. (+ 1 (inc-rec (- n 1))))) • Each recursive method call is a tail call -- i.e., a method call with > (inc-rec 1000000) ; 10^6 no pending opera*ons aSer the call. When all recursive calls of 1000001 a method are tail calls, it is said to be tail recursive. A tail recursive method is one way to specify an itera*ve process. > (inc-rec 10000000) ; 10^7 Itera*on is so common that most programming languages provide Eventually run out special constructs for specifying it, known as loops. of stack space Iteration/Tail Recursion 7 Iteration/Tail Recursion 8

  3. inc_rec in Python inc-iter / inc-tail in Racket (define (inc-iter n) def inc_rec (n): (inc-tail n 1)) if n == 0: return 1 (define (inc-tail num resultSoFar) else: (if (= num 0) return 1 + inc_rec(n - 1) resultSoFar (inc-tail (- num 1) (+ resultSoFar 1)))) In [16]: inc_rec(100) Out[16]: 101 > (inc-iter 10000000) ; 10^7 10000001 In [17]: inc_rec(1000) … in inc_rec(n) > (inc-iter 100000000) ; 10^8 Very small maximum 9 return 1 10 else: recursion depth 100000001 ---> 11 return 1 + inc_rec(n – 1) (implementation dependent) RuntimeError: maximum recursion depth exceeded Will inc-iter ever run out of memory? Iteration/Tail Recursion 9 Iteration/Tail Recursion 10 inc_iter / int_tail in Python Why the Difference? def inc_iter (n): # Not really iterative! it(0,4) it(0,4): 4 return inc_tail(n, 1) it(1,3) it(1,3) it(1,3) it(1,3): 4 def inc_tail(num, resultSoFar): it(2,2) it(2,2) it(2,2) it(2,2) it(2,2) it(2,2): 4 if num == 0: return resultSoFar it(3,1) it(3,1) it(3,1) it(3,1) it(3,1) it(3,1) it(3,1) it(3,1): 4 else: Python pushes a stack frame for every call to iter_tail. When iter_tail(0,4) returns return inc_tail(num - 1, resultSoFar + 1) the answer 4, the stacked frames must be popped even though no other work remains to be done coming out of the recursion. Although tail recursion In [19]: inc_iter(100) expresses iteration in Racket (and SML), it does *not* Out[19]: 101 express iteration in Python it(3,1) it(2,2) it(1,3) it(0,4) it(0,4): 4 (or JavaScript, C, Java, etc.) In [19]: inc_iter(1000) … Racket’s tail-call op*miza*on replaces the current stack frame with a new stack RuntimeError: maximum recursion depth exceeded frame when a tail call (func*on call not in a subexpression posi*on) is made. When iter-tail(0,4) returns 4, no unnecessarily stacked frames need to be popped! Iteration/Tail Recursion 11 Iteration/Tail Recursion 12

  4. Origins of Tail Recursion What to do in Python (and most other languages)? In Python, must re-express the tail recursion as a loop! def inc_loop (n): resultSoFar = 0 while n > 0: n = n - 1 Guy Lewis Steele resultSoFar = resultSoFar + 1 a.k.a. ``The Great Quux” return resultSoFar In [23]: inc_loop(1000) # 10^3 One of the most important but least appreciated CS papers of all *me Out[23]: 1001 • • Treat a func*on call as a GOTO that passes arguments In [24]: inc_loop(10000000) # 10^8 Out[24]: 10000001 • Func*on calls should not push stack; subexpression evalua*on should! But Racket doesn’t need loop constructs because tail recursion Looping constructs are unnecessary; tail recursive calls are a more general • suffices for expressing itera*on! and elegant way to express itera*on. Iteration/Tail Recursion 13 Iteration/Tail Recursion 14 Itera*ve factorial: Python while loop version while loop factorial: Execu*on Land Itera*on Rules: Execu'on frame for fact_while(4) • next num is previous num minus 1. n num prod • next prod is previous num *mes previous prod. 4 4 1 num = n 4 3 def fact_while(n): prod = 1 2 12 while (num > 0): 24 num = n 1 prod = num * prod Declare/ini'alize local num = num - 1 prod = 1 state variables 0 24 return prod while (num > 0): step num prod prod = num * prod Calculate product and 1 4 1 num = num - 1 decrement num 2 3 4 3 2 12 4 1 24 return prod Don � t forget to return answer! 5 0 24 Iteration/Tail Recursion 15 Iteration/Tail Recursion 16

  5. Rela*ng Tail Recursion and while loops Gotcha! Order of assignments in loop body (define (fact-iter n) What’s wrong with the following loop version of factorial? (fact-tail n 1)) Ini'alize def fact_while(n): In [23]: fact_while(4) variables num = n Out[23]: 6 (define (fact-tail num prod) prod = 1 (if (= num 0) while (num > 0): prod num = num - 1 (fact-tail (- num 1) (* num prod)))) prod = num * prod return prod Moral: must think carefully about order of assignments in loop body! While def fact_while(n): not done, num = n update prod = 1 (define (fact-tail num prod ) variables while (num > 0): When done, Note: (if (= num 0) prod = num * prod return ans tail recursion num = num – 1 doesn’t have ans return prod this gotcha! (fact-tail (- num 1) (* num prod)))) Iteration/Tail Recursion 17 Iteration/Tail Recursion 18 Recursive Fibonacci Itera*on leads to a more efficient Fib The Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, … (define (fib-rec n) ; returns rabbit pairs at month n Itera*on table for calcula*ng the 8th Fibonacci number: (if (< n 2) ; assume n >= 0 n n i fibi fibi+1 (+ (fib-rec (- n 1)) ; pairs alive last month 8 0 0 1 (fib-rec (- n 2)) ; newborn pairs 8 1 1 1 ))) 8 2 1 2 fib(4) : 3 + 8 3 2 3 8 4 3 5 fib(3) : 2 fib(2) : 1 + + 8 5 5 8 8 6 8 13 fib(2) : 1 fib(1) : 1 fib(1) : 1 fib(0) : 0 + 8 7 13 21 fib(1) : 1 fib(0) : 0 8 8 21 34 Iteration/Tail Recursion 19 Iteration/Tail Recursion 20

Recommend


More recommend