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 Fall 2016, Lyn Turbak Department of Computer Science Wellesley College Iteration/Tail Recursion 2 An itera*ve approach to factorial Factorial Revisited Invocation Tree State Variables: Idea: multiply (fact-rec 4): 24 (define (fact-rec n) • num is the current number being processed. on way down (if (= n 0) -1 4 1 • ans is the product of all numbers already processed. 1 * (* n (fact-rec (- n 1))))) (fact-rec 3): 6 -1 * Iteration Table: -1 * step num ans 3 4 pending multiplication 1 4 1 is nontrivial glue step divide glue (fact-rec 2): 2 -1 2 3 4 * divide 3 2 12 -1 * 2 12 4 1 24 (fact-rec 1): 1 5 0 24 -1 * -1 * 1 24 Iteration Rules: (fact-rec 0): 1 • next num is previous num minus 1. -1 * • next ans is previous num times previous ans. Iteration/Tail Recursion 3 Iteration/Tail Recursion 4 24 0
Tail-recursive factorial: invocation tree Itera*ve factorial: tail recursive version ;; Here, and in many tail recursions, need a wrapper Iteration Rules: ;; function to initialize first row of iteration • next num is previous num minus 1. ;; table. E.g., invoke (fact-iter 4) to calculate 4! Invocation Tree: • next ans is previous num times previous ans. (define (fact-iter n) (fact-tail n 1)) (fact-iter 4) (define (fact-tail num ans ) (define (fact-tail num ans) (if (= num 0) (fact-iter 4 1) (if (= num 0) ans ans stopping divide (fact-tail (- num 1) (* num ans)))) (fact-tail (- num 1) (* num ans)))) (fact-iter 3 4) condition Iteration Table: (fact-iter 2 12) step num ans ;; Here, and in many tail recursions, need a wrapper (fact-iter 1 24) 1 4 1 ;; function to initialize first row of iteration 2 3 4 (fact-iter 0 24) ;; table. E.g., invoke (fact-iter 4) to calculate 4! 3 2 12 (define (fact-iter n) no glue! 4 1 24 (fact-tail n 1)) 5 0 24 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 ; function for testing Racket stack memory limits • A process is iterative if it can be expressed as a sequence of (define (inc-rec n) steps that is repeated until some stopping condition is reached. (if (= n 0) • In divide/conquer/glue methodology, an iterative 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 no pending operations after the call. When all recursive calls of a method are tail calls, it is said to be tail recursive. > (inc-rec 1000000) ; 10^6 A tail recursive method is one way to specify an iterative 1000001 process. > (inc-rec 10000000) Iteration is so common that most programming languages provide ; 10^7 special constructs for specifying it, known as loops. Iteration/Tail Recursion 7 Iteration/Tail Recursion 8
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 In [17]: inc_rec(1000) 10000001 … /Users/fturbak/Desktop/lyn/courses/cs251-archive/cs251-s16/slides-lyn-s16/racket-tail/iter.py in inc_rec(n) > (inc-iter 100000000) ; 10^8 9 return 1 10 else: 100000001 ---> 11 return 1 + inc_rec(n - 1) 12 # inc_rec(10) => 11 13 # inc_rec(100) => 101 Will inc-iter ever run out of memory? RuntimeError: maximum recursion depth exceeded 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. In [19]: inc_iter(100) Out[19]: 101 it(3,1) It(2,2) It(1,3) It(0,4) It(0,4): 4 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
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 ans • next ans is previous num *mes previous ans. 4 4 1 num = n 4 3 def fact_while(n): ans = 1 2 12 while (num > 0): 24 num = n 1 ans = num * ans Declare/ini=alize local num = num - 1 ans = 1 state variables 0 24 return ans while (num > 0): step num ans ans = num * ans Calculate product and 1 4 1 num = num - 1 decrement num 2 3 4 3 2 12 4 1 24 return ans Don � t forget to return answer! 5 0 24 Iteration/Tail Recursion 15 Iteration/Tail Recursion 16
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): variables num = n (define (fact-tail num ans) ans = 1 (if (= num 0) while (num > 0): ans num = num - 1 (fact-tail (- num 1) (* num ans)))) ans = num * ans return ans Moral: must think carefully about order of assignments in loop body! def fact_while(n): While num = n not done, ans = 1 update (define (fact-tail num ans ) while (num > 0): When done, Note: variables (if (= num 0) num = num - 1 return ans tail recursion ans = num * ans doesn’t have ans return ans this gotcha! (fact-tail (- num 1) (* num ans)))) 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 fib_i fib_i_plus_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
Itera*ve Fibonacci in Racket Gotcha! Assignment order and temporary variables What’s wrong with the following looping versions of Fibonacci? Flesh out the missing parts def fib_for1(n): fib_i= 0 (define (fib-iter n) fib_i_plus_1 = 1 (fib-tail … )) for i in range(n): fib_i = fib_i_plus_1 (define (fib-tail n i fib_i fib_i_plus_1) fib_i_plus_1 = fib_i + fib_i_plus_1 … return fib_i def fib_for2(n): fib_i= 0 fib_i_plus_1 = 1 for i in range(n): fib_i_plus_1 = fib_i + fib_i_plus_1 ) fib_i = fib_i_plus_1 return fib_i Moral: some*mes no order of assignments to state variables in a loop is correct and it is necessary to introduce one or more temporary variables to save the previous value of a variable for use in the right-hand side of a later assignment. Iteration/Tail Recursion 21 Or can use simultaneous assignment in languages that have it (like Python!) Iteration/Tail Recursion 22 Fixing Gotcha Iterative list summation 1. Use a temporary variable (in general, might need n-1 such vars for n state variables def fib_for_fixed1(n): fib_i= 0 Ø L 6 3 -22 5 fib_i_plus_1 = 1 for i in range(n): fib_i_prev = fib_i Iteration table fib_i = fib_i_plus_1 fib_i_plus = fib_i_prev + fib_i_plus_1 L result return fib_i ‘(6 3 -22 5) 0 ‘(3 -22 5) 6 2. Use simultaneous assignment: ‘(-22 5) 9 def fib_for_fixed2(n): ‘(5) -13 fib_i= 0 ‘() -8 fib_i_plus_1 = 1 for i in range(n): (fib_i, fib_i_plus_1) =\ (fib_i_plus_1, fib_i + fib_i_plus_1) return fib_i Iteration/Tail Recursion 23 Iteration/Tail Recursion 24
Recommend
More recommend