Tail Recursion. Problems with Recursion Recursion is generally favored over iteration in Scheme and many other languages – It’s elegant, minimal, can.

Slides:



Advertisements
Similar presentations
Introduction to Recursion and Recursive Algorithms
Advertisements

1 Scheme and Functional Programming Aaron Bloomfield CS 415 Fall 2005.
The University of Adelaide, School of Computer Science
COMPILERS Register Allocation hussein suleman uct csc305w 2004.
Lists in Lisp and Scheme a. Lists are Lisp’s fundamental data structures, but there are others – Arrays, characters, strings, etc. – Common Lisp has moved.
Types of Recursive Methods
Tail Recursion. Problems with Recursion Recursion is generally favored over iteration in Scheme and many other languages – It’s elegant, minimal, can.
The University of Adelaide, School of Computer Science
Functional Programming. Pure Functional Programming Computation is largely performed by applying functions to values. The value of an expression depends.
Recursion vs. Iteration The original Lisp language was truly a functional language: –Everything was expressed as functions –No local variables –No iteration.
Tail Recursion. Problems with Recursion Recursion is generally favored over iteration in Scheme and many other languages It’s elegant, minimal, can be.
CSE341: Programming Languages Lecture 6 Tail Recursion, Accumulators, Exceptions Dan Grossman Fall 2011.
Computer Science II Recursion Professor: Evan Korth New York University.
More on Recursive Methods KFUPM- ICS Data Structures.
Recursion Road Map Introduction to Recursion Recursion Example #1: World’s Simplest Recursion Program Visualizing Recursion –Using Stacks Recursion Example.
28/06/2015CMPUT Functions (2)  Function calling convention  Various conventions available  One is specified by CMPUT229  Recursive functions.
Functional programming: LISP Originally developed for symbolic computing First interactive, interpreted language Dynamic typing: values have types, variables.
CSC 313 – Advanced Programming Topics. Why Recurse?  Recursion is useful, powerful technique  Often yields compact, easy-to-read code  Highlights all.
CS 152: Programming Language Paradigms February 24 Class Meeting Department of Computer Science San Jose State University Spring 2014 Instructor: Ron Mak.
Copyright © 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Chapter 12: Recursion Problem Solving, Abstraction, and Design using C++
Stacks & Recursion. Stack pushpop LIFO list - only top element is visible top.
Chapter 9: Recursion1 CHAPTER 9 RECURSION. Recursion  Concept of recursion  A recursive: Benefit and Cost  Comparison : Iterative and recursive functions.
Fall Week 3 CSCI-141 Scott C. Johnson.  Say we want to draw the following figure ◦ How would we go about doing this?
Nonvisual Arrays and Recursion by Chris Brown under Prof. Susan Rodger Duke University June 2012.
Chapter 12 Recursion, Complexity, and Searching and Sorting
Stephen P. Carl - CS 2421 Recursion Reading : Chapter 4.
1 CS 177 Week 16 Recitation Recursion. 2 Objective To understand and be able to program recursively by breaking down a problem into sub problems and joining.
224 3/30/98 CSE 143 Recursion [Sections 6.1, ]
ICS220 – Data Structures and Algorithms Dr. Ken Cosh Week 5.
CSC 221: Recursion. Recursion: Definition Function that solves a problem by relying on itself to compute the correct solution for a smaller version of.
Edited by Malak Abdullah Jordan University of Science and Technology Data Structures Using C++ 2E Chapter 6 Recursion.
Data Structures R e c u r s i o n. Recursive Thinking Recursion is a problem-solving approach that can be used to generate simple solutions to certain.
Java Programming: Guided Learning with Early Objects Chapter 11 Recursion.
Prolog Program Style (ch. 8) Many style issues are applicable to any program in any language. Many style issues are applicable to any program in any language.
CS535 Programming Languages Chapter - 10 Functional Programming With Lists.
Lisp and Scheme I. Versions of LISP LISP is an acronym for LISt Processing language Lisp is an old language with many variants – Fortran is the only older.
Chapter 15: Recursion. Objectives In this chapter, you will: – Learn about recursive definitions – Explore the base case and the general case of a recursive.
Chapter 15: Recursion. Recursive Definitions Recursion: solving a problem by reducing it to smaller versions of itself – Provides a powerful way to solve.
Chapter 15: Recursion. Objectives In this chapter, you will: – Learn about recursive definitions – Explore the base case and the general case of a recursive.
Preocedures A closer look at procedures. Outline Procedures Procedure call mechanism Passing parameters Local variable storage C-Style procedures Recursion.
CSC 143 P 1 CSC 143 Recursion [Chapter 5]. CSC 143 P 2 Recursion  A recursive definition is one which is defined in terms of itself  Example:  Compound.
Recursion Chapter 2 Objectives Upon completion you will be able to:
CS314 – Section 5 Recitation 9
Lets and Loops Tail Recursion.
Functional Programming
Storage Allocation Mechanisms
CS314 – Section 5 Recitation 10
Chapter 15 Recursion.
Tail Recursion.
Introduction to Recursion
Recursion Lakshmish Ramaswamy.
Data Structures and Algorithms
CS 326 Programming Languages, Concepts and Implementation
Chapter 15 Recursion.
CSE 341 Lecture 5 efficiency issues; tail recursion; print
Java 4/4/2017 Recursion.
CS 326 Programming Languages, Concepts and Implementation
Lists in Lisp and Scheme
Closures and Streams cs784(Prasad) L11Clos
6.001 SICP Stacks and recursion
Recursion "To understand recursion, one must first understand recursion." -Stephen Hawking.
Recursion Chapter 11.
Stacks & Recursion.
Streams, Delayed Evaluation and a Normal Order Interpreter
Recursion Taken from notes by Dr. Neil Moore
Java Programming: Chapter 9: Recursion Second Edition
Announcements Quiz 5 HW6 due October 23
Yan Shi CS/SE 2630 Lecture Notes
Where is all the knowledge we lost with information? T. S. Eliot
CSc 453 Interpreters & Interpretation
Presentation transcript:

Tail Recursion

Problems with Recursion Recursion is generally favored over iteration in Scheme and many other languages – It’s elegant, minimal, can be implemented with regular functions and easier to analyze formally – Some languages don’t have iteration (Prolog) It can also be less efficient more functional calls and stack operations (context saving and restoration) Running out of stack space leads to failure deep recursion

Tail recursion is iteration Tail recursion is a pattern of use that can be compiled or interpreted as iteration, avoiding the inefficiencies Tail recursion A tail recursive function is one where every recursive call is the last thing done by the function before returning and thus produces the function’s value More generally, we identify some proceedure calls as tail callstail calls

Tail Call A tail call is a procedure call inside another procedure that returns a value which is then immediately returned by the calling procedure def foo(data): bar1(data) return bar2(data) def foo(data): if test(data): return bar2(data) else: return bar3(data) A tail call need not come at the textual end of the procedure, but at one of its logical ends

Tail call optimization When a function is called, we must remember the place it was called from so we can return to it with the result when the call is complete This is typically stored on the call stack There is no need to do this for tail calls Instead, we leave the stack alone, so the newly called function will return its result directly to the original caller

Scheme’s top level loop Consider a simplified version of the REPL (define (repl) (printf “> “) (print (eval (read))) (repl)) This is an easy case: with no parameters there is not much context

Scheme’s top level loop 2 Consider a fancier REPL (define (repl) (repl1 0)) (define (repl1 n) (printf “~s> “ n) (print (eval (read))) (repl1 (add1 n))) This is only slightly harder: just modify the local variable n and start at the top

Scheme’s top level loop 3 There might be more than one tail recursive call (define (repl1 n) (printf “~s> “ n) (print (eval (read))) (if (= n 9) (repl1 0) (repl1 (add1 n)))) What’s important is that there’s nothing more to do in the function after the recursive calls

Two skills Distinguishing a trail recursive call from a non tail recursive one Being able to rewrite a function to eliminate its non-tail recursive calls

Simple Recursive Factorial (define (fact1 n) ;; naive recursive factorial (if (< n 1) 1 (* n (fact1 (sub1 n)) ))) Is this a tail call? No. It must be called and its value returned before the multiplication can be done

Tail recursive factorial (define (fact2 n) ; rewrite to just call the tail-recursive ; factorial with the appropriate initial values (fact2.1 n 1)) (define (fact2.1 n accumulator) ; tail recursive factorial calls itself ; as last thing to be done (if (< n 1) accumulator (fact2.1 (sub1 n) (* accumulator n)) )) Is this a tail call? Yes. Fact2.1’s args are evalua- ted before it’s called.

Trace shows what’s going on > (requireracket/trace) > (load "fact.ss") > (trace fact1) > (fact1 6) |(fact1 6) | (fact1 5) | |(fact1 4) | | (fact1 3) | | |(fact1 2) | | | (fact1 1) | | | |(fact1 0) | | | |1 | | | 1 | | |2 | | 6 | |24 | 120 |

fact2 > (trace fact2 fact2.1) > (fact2 6) |(fact2 6) |(fact ) |(fact ) |(fact ) |(fact ) |(fact ) |(fact ) |(fact ) | Interpreter & compiler note the last expression to be evaled & returned in fact2.1 is a recursive call Instead of pushing state on the sack, it reassigns the local variables and jumps to beginning of the procedure Thus, the recursion is automatically transformed into iteration

Reverse a list This version works, but has two problems (define (rev1 list) ; returns the reverse a list (if (null? list) empty (append (rev1 (rest list)) (list (first list)))))) It is not tail recursive It creates needless temporary lists

A better reverse (define(rev2 list) (rev2.1 list empty)) (define (rev2.1 list reversed) (if (null? list) reversed (rev2.1 (rest list) (cons (first list) reversed))))

rev1 and rev2 > (load "reverse.ss") > (trace rev1 rev2 rev2.1) > (rev1 '(a b c)) |(rev1 (a b c)) | (rev1 (b c)) | |(rev1 (c)) | | (rev1 ()) | | () | |(c) | (c b) |(c b a) (c b a) > (rev2 '(a b c)) |(rev2 (a b c)) |(rev2.1 (a b c) ()) |(rev2.1 (b c) (a)) |(rev2.1 (c) (b a)) |(rev2.1 () (c b a)) |(c b a) (c b a) >

The other problem Append copies the top level list structure of it’s first argument. (append ‘(1 2 3) ‘(4 5 6)) creates a copy of the list (1 2 3) and changes the last cdr pointer to point to the list (4 5 6) In reverse, each time we add a new element to the end of the list, we are (re-)copying the list.

Append (two args only) (define (append list1 list2) (if (null? list1) list2 (cons (first list1) (append (rest list1) list2))))

Why does this matter? The repeated rebuilding of the reversed list is needless work It uses up memory and adds to the cost of garbage collection (GC) garbage collection GC adds a significant overhead to the cost of any system that uses it Experienced programmers avoid algorithms that needlessly consume memory that must be garbage collected

Fibonacci Another classic recursive function is computing the nth number in the fibonacci seriesfibonacci series (define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) But its grossly inefficient – Run time for fib(n) ≅ O(2 n ) – (fib 100) can not be computed this way Are the tail calls?

This has two problems That recursive calls are not tail recursive is the least of its problems It also needlessly recomputes many values fib(6) Fib(5) Fib(4) Fib(3) Fib(2) Fib(3) Fib(2) Fib(1)

Trace of (fib 6) > (fib 6) > (fib 5) > >(fib 4) > > (fib 3) > > >(fib 2) > > > (fib 1) < < < 1 > > > (fib 0) < < < 0 < < <1 > > >(fib 1) < < <1 < < 2 > > (fib 2) > > >(fib 1) < < <1 > > >(fib 0) < < <0 < < 1 < <3 > >(fib 3) > > (fib 2) > > >(fib 1) < < <1 > > >(fib 0) < < <0 < < 1 > > (fib 1) < < 1 < <2 < 5 > (fib 4) > >(fib 3) > > (fib 2) > > >(fib 1) < < <1 > > >(fib 0) < < <0 < < 1 > > (fib 1) < < 1 < <2 > >(fib 2) > > (fib 1) < < 1 > > (fib 0) < < 0 < <1 < 3 <8 8 >

Tail-recursive version of Fib Here’s a tail-recursive version that runs in 0(n) (define (fib2 n) (cond ((= n 0) 0) ((= n 1) 1) (#t (fib-tr n 2 0 1)))) (define (fib-tr target n f2 f1 ) (if (= n target) (+ f2 f1) (fib-tr target (+ n 1) f1 (+ f1 f2)))) We pass four args: n is the current index, target is the index of the number we want, f2 and f1 are the two previous fib numbers

Trace of (fib2 10) > (fib2 10) >(fib-tr ) >(fib-tr ) >(fib-tr ) >(fib-tr ) >(fib-tr ) >(fib-tr ) >(fib-tr ) >(fib-tr ) >(fib-tr ) < is the target, 5 is the current index fib(3)=2 and fib(4)=3 Stop when current index equals target and return sum of last two args

Compare to an iterative version The tail recursive version passes the “loop variables” as arguments to the recursive calls It’s just a way to do iteration using recursive functions without the need for special iteration operators def fib(n): if n < 3: return 1 else: f2 = f1 = 1 x = 3 while x<n: f1, f2 = f1 + f2, f1 return f1 + f2 def fib(n): if n < 3: return 1 else: f2 = f1 = 1 x = 3 while x<n: f1, f2 = f1 + f2, f1 return f1 + f2

No tail call elimination in many PLs Many languages don’t optimize tail calls, including C, Java and Python Recursion depth is constrained by the space allocated for the call stack This is a design decision that might be justified by the worse is better principleworse is better See Guido van Rossum’s comments on TRETRE

Python example > def dive(n=1):... print n,... dive(n+1)... >>> dive() Traceback (most recent call last): File " ", line 1, in File " ", line 3, in dive more lines... File " ", line 3, in dive RuntimeError: maximum recursion depth exceeded >>>

Conclusion Recursion is an elegant and powerful control mechanism We don’t need to use iteration We can eliminate any inefficiency if we Recognize and optimize tail-recursive calls, turning recursion into iteration Some languages (e.g., Python) choose not to do this, and advocate using iteration when appropriate But side-effect free programming remains easier to analyze and parallelize