Call Trees

fun s u m l i s t n i l = 0 | s u m l i s t ( x : : xs ) = x + s u m l i s t xs

(Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary

has a linear call-tree
sum list ([2 ,1]) | sum list ([1]) | sum list ( nil ) fun f i b 0 = 0 | fib 1 = 1 | f i b n = f i b ( n−1) + f i b ( n−2)

has a non-linear (branching) call-tree
f i b (3) / \ f i b (2) f i b (1) / \ f i b (0) f i b (1)

3] | | x 1 | +−−−−−−−−−−−−−−−−−+ | L [1 . 3 ] ) . (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary Environment during recursion: (see p. 3 ] ) . . added i n r e v e r s e ( [ 1 . 2 .. added i n r e v e r s e ( n i l ) . . .2 . . − reverse (L ) . top l e v e l environment . added i n r e v e r s e ( [ 2 .3] | | . | +−−−−−−−−−−−−−−−−−+ . . added i n r e v e r s e ( [ 3 ] ) ..Stacking Bindings fun r e v e r s e n i l = n i l | r e v e r s e ( x : : xs ) = r e v e r s e xs @ [ x ] − val L = [ 1 . 3 ] . 2 . 67) +−−−−−−−−−−−−−−−−−+ | | +−−−−−−−−−−−−−−−−−+ | xs nil | | x 3 | +−−−−−−−−−−−−−−−−−+ | xs [3] | | x 2 | +−−−−−−−−−−−−−−−−−+ | xs [2 .

. 2. n − 1. . n the running time is thus quadratic. . . n − 2.Running Time (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples fun r e v e r s e n i l = n i l | r e v e r s e ( x : : xs ) = ( r e v e r s e xs ) @ [ x ] Summary Consider calling reverse on a list of length n it makes n calls to append which takes time 1.

000 100.000 20.Performance Test We need generator of large data: fun from i j = i f i > j then n i l e l s e i : : from ( i +1) j (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary Execute reverse L where L is the value of (from 1 n) n 10.000 running time 2 seconds 7 seconds 34 seconds very slow When testing sum list.000 40. we rather want fun o n e s 0 = n i l | o n e s n = 1 : : o n e s ( n−1) .

Assessment (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion fun r e v e r s e n i l = n i l | r e v e r s e ( x : : xs ) = ( r e v e r s e xs ) @ [ x ] Further Examples Summary Why must we call append? :: only allows us to add items in front of list reverse does non-trivial computation only when going up the tree We might consider doing computation when going down the tree .

Passing Results Down In Call Tree Recall that list reversal is special case of foldl fun f o l d l f e n i l = e | f o l d l f e ( x : : xs ) = f o l d l f ( f (x . e )) xs fun m y r e v e r s e x s = f o l d l op : : n i l xs . informing the recursion at the next level of something that we have accumulated at the current level . (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary Specializing foldl wrt op:: yields fun r e v a c c e n i l = e | rev acc e ( x : : xs ) = rev acc ( x : : e ) xs fun r e v e r s e a c c x s = r e v a c c n i l x s e holds “the results so far” e is flowing down the tree.

000. Since reverse acc uses no append.000 100. When called on the value of from 1 n n 10.000 reverse 2 seconds 7 seconds very slow infeasible reverse acc instantaneous instantaneous instantaneous 3 seconds Tail Recursion Further Examples Summary .Performance Comparison (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Recall that reverse had quadratic running time.000 1.000 20. we expect linear running time.

Tail Recursion (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion fun r e v a c c e n i l = e | rev acc e ( x : : xs ) = rev acc ( x : : e ) xs Further Examples Summary This function is tail recursive: no computation happens after the recursive call value of recursive call is the return value thus. no variables are referenced after recursive call This kind of recursion is actually iteration in disguise! .

acc = [ ] .Iterative Reverse fun r e v a c c e n i l = e | rev acc e ( x : : xs ) = rev acc ( x : : e ) xs (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion can be converted to “pseudo-C (renaming e to acc): l i s t r e v e r s e ( xs : l i s t ) { l i s t acc . w h i l e ( x s != n i l ) do { a c c = hd ( x s ) : : a c c . xs = t l ( xs ) . } Further Examples Summary acc holds result xs and acc are updated each time through the loop . } return acc .

but not in v. we do thus not need to stack variable bindings for the recursive calls parameter passing can be implemented in the compiler by destructive updates (that is.2 for tail-recursive functions.2 for tail-recursive functions. assignment)! Computation occurs after recursion in v. but not in v. we do thus not need to stack return addresses.1.Tail Recursion versus Non-Tail Recursion (∗ v e r s i o n 1 : without accumulator ∗) fun r e v e r s e n i l = n i l | r e v e r s e ( x : : xs ) = r e v e r s e xs @ [ x ] (∗ v e r s i o n 2 : with accumulator ∗) fun r e v a c c e n i l = e | rev acc e ( x : : xs ) = rev acc ( x : : e ) xs (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary x is used after recursion in v.1. a call can be implemented in the compiler as a goto! .

. { .. . y n ) = . . . .. .. .. . .... y n) { while . y 1 = <exp −1>.. . .. . } } ... .Parameter “Assignment” The tail-recursive function fun f ( y 1 .. y n = <exp−n >.is roughly equivalent to.. f (<exp −1>. . f(y 1 .. <exp−n>) (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary . . ..

Top-level call: sum list acc 0 xs Performance comparison on the value of ones n n 4. fun s u m l i s t a c c a c c n i l = a c c | s u m l i s t a c c acc ( x : : xs ) = s u m l i s t a c c ( x+a c c ) x s Tail Recursion Further Examples Summary Summation proceeds while moving left to right.000.Converting SumList to Tail Recursion fun s u m l i s t n i l = 0 | s u m l i s t ( x : : xs ) = x + s u m l i s t xs (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators The recursive calls are unfolded until we reach the end of the list.000. from where we then move to the left while summing the results.000 sum list 5 seconds 21 seconds sum list acc instantaneous instantaneous .000 5.

/∗ e s c a p e ∗/ else a c c = hd ( x s ) ∗ a c c . } . w h i l e ( x s != n i l ) do { i f ( hd ( x s ) = 0 ) t h e n return 0.Tail-Recursive MultList fun m u l t l i s t a c c a c c n i l = a c c | m u l t l i s t a c c acc ( x : : xs ) = m u l t l i s t a c c ( x∗ acc ) xs (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary Question: what happens if we hit a 0? fun m u l t l i s t a c c e x i t a c c n i l = a c c | m u l t l i s t a c c e x i t acc ( x : : xs ) = i f x = 0 then 0 e l s e m u l t l i s t a c c e x i t ( x∗ acc ) xs In C. we might have int m u l t l i s t ( xs : l i s t ) { i n t acc . acc = 1 . } r e t u r n acc . xs = t l ( xs ) .

and can be made tail-recursive by using two accumulating parameters: fun f i b a c c p r e v c u r r n = i f n = 1 then c u r r e l s e f i b a c c c u r r ( p r e v+c u r r ) ( n−1) fun f i b o n a c c i a c c n = i f n = 0 then 0 e l s e f i b a c c 0 1 n Performance comparison n 42 43 44 fib 7 seconds 11 seconds 17 seconds fibonacci acc instantaneous instantaneous instantaneous .Making Fibonacci Tail-Recursive fun f i b 0 = 0 | fib 1 = 1 | f i b n = f i b ( n−2) + f i b ( n−1) (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples Summary has a branching call-tree.

F (k+1)) = g (n−1. F (k). F (k+1). F (n) = F (n − 2) + F (n − 1) (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators Tail Recursion Further Examples which can be tail-recursively implemented by fun g ( n . F (k + 1)) = F (n + k) This can be proved by induction in n. p r e v+c u r r ) Summary Correctness Lemma: for all n ≥ 1. 0. F (k+2)) = F ((n−1)+(k+1)) = F (n+k) Thus F (n) = g (n. F (k). F (0). F (k+1). c u r r ) = i f n = 1 then c u r r e l s e g ( n −1. k ≥ 0: g (n. g (n. F (k)+F (k+1)) = g (n−1. . 1). for the inductive case. the base case is n = 1 which is obvious. c u r r . p r e v . F (1)) = g (n.Correctness of Tail-Recursive Fibonacci With F the fibonacci function we have F (0) = 0. F (1) = 1. n > 1.

Summary (Tail) Recursion Amtoft from Hatcliff Run-Time Structures Accumulators a tail-recursive function is one where the function performs no computation after the recursive call a good SML compiler will detect tail-recursive functions and implement them iteratively as loops there is no need to stack bindings or return addresses recursive calls become gotos we can think of arguments as being “assigned to” (destructively update) formal parameters. Tail Recursion Further Examples Summary this substantially reduces execution time and space (for stack) overhead .

Sign up to vote on this title
UsefulNot useful