Class 38: Tail Recursion

Back to Pause for Breath. On to Tail Recursion, Continued.

Held Monday, November 11, 2002

Summary

Today we revisit recursion and consider a more efficient way to write recursive procedures. This new technique is called tail recursion.

Related Pages

Notes

• Any questions on exam 2?
• I've fixed all the errors on the exam and in the sample code I've found (or heard about) so far. You're up to three points of extra credit on the exam.
• Is anyone here planning on doing the indpendent study I mentioned? I haven't announced it outside of this class yet, and am thinking of waiting until next year.
• Today's topic is important enough that we're spending two class periods on it.

Overview

• Kinds of recursion
• Why do tail recursion
• Generating lists tail-recursively
• Lab

Two Kinds of Recursion

• Recursion in which you do something with the recursive result.
• E.g., definition of `add-to-all`, `factorial`, traditional right-associative `sum`.
• Recursion in which you simply return the recursive result, unchanged.
• E.g., `member?`, the left-associative `sum`.
• The latter kind of recursion is called tail recursion and, in Scheme, is likely to be faster. Why?
• Let's look at the two versions of `sum` (in class).
• Version 1, right associative, not tail recursive.
```(define sumr
(lambda (values)
(if (null? values) 0
(+ (car values) (sumr (cdr values))))))
```
```  + Version 2, left associative, tail recursive.
(define suml
(lambda (values)
(suml-helper values 0)))
(define suml-helper
(lambda (remaining-values partial-sum)
(if (null? remaining-values) partial-sum
(suml-helper (cdr remaining-values)
(+ partial-sum
(car remaining-values))))))
```
• Let's sum the list `(1 2 3 4)` and see if one seems to be easier to deal with.
• Right associative
```(sumr (1 2 3 4))
--> (+ 1 (sumr (2 3 4)))
--> (+ 1 (+ 2 (sumr (3 4))))
--> (+ 1 (+ 2 (+ 3 (sumr (4)))))
--> (+ 1 (+ 2 (+ 3 (+ 4 (sumr ())))))
--> (+ 1 (+ 2 (+ 3 (+ 4 0))))
--> (+ 1 (+ 2 (+ 3 4)))
--> (+ 1 (+ 2 7))
--> (+ 1 9)
--> 10
```
• Left associative
```(suml (1 2 3 4))
--> (suml-helper (1 2 3 4) 0)
--> (suml-helper (2 3 4) 1)
--> (suml-helper (3 4) 3)
--> (suml-helper (4) 6)
--> (suml-helper () 10)
--> 10
```
• Which would you prefer to evaluate by hand?
• In the right-associative case, Scheme needs to remember the things to do once the recursion reaches the base case.
• Remembering that stuff takes extra memory and time.
• Hence, tail-recursive procedures are normally quicker than non-tail-recursive procedures.

Making Procedures Tail Recursive

• The normal strategy for making procedures tail recursive is the one we used for `suml`:
• Make a helper procedure with one additional parameter.
• The additional parameter accumulates partial solutions. Hence, we often call it an accumulator.
• You can often transform a non-tail-recursive (but recursive) procedure in to a tail-recursive procedure:
• Build that helper with an extra parameter.
• The accumulator normally starts out with the value from the old base case (the one in the non-tail-recursive procedure).
• The operation used to update the accumulator is similar to (but not necessarily identical to) the one used in the standard recursive call.

Tail Recursion and List Construction

• While tail recursion is generally a good thing, it's a bad idea to spend lots of extra computational effort on building the accumulated partial result.
• The problem appears most frequently in procedures that build lists.
• Consider the problem of adding two to each value in a list.
• Suppose our accumulator contains all the values we've added two to.
• Then at each step, we'll need to put the next value at the end of the accumulator.
• How do you add to the end of a list?
• How much computational effort does it involve?
• You'll find that your procedure spends a lot of time walking through the accumulated result.
• Can we do something better? Yes. Accumulate the reverse of the desired result.
• Updating at each step is each; just cons to the front.
• We do need to spend some extra effort at the end to reverse that result.
• Alternately, we can reverse the parameter before calling the helper.
• See the reading for more details.

History

Thursday, 29 August 2002 [Samuel A. Rebelsky]

Monday, 11 November 2002 [Samuel A. Rebelsky]

• Filled in some introductory details.

Back to Pause for Breath. On to Tail Recursion, Continued.

Disclaimer: I usually create these pages on the fly, which means that I rarely proofread them and they may contain bad grammar and incorrect details. It also means that I tend to update them regularly (see the history for more details). Feel free to contact me with any suggestions for changes.

This document was generated by Siteweaver on Mon Dec 2 08:41:49 2002.
The source to the document was last modified on Mon Nov 11 08:24:19 2002.
This document may be found at `http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2002F/Outlines/outline.38.html`.

You may wish to validate this document's HTML ; ; Check with Bobby

Samuel A. Rebelsky, rebelsky@grinnell.edu