Programming Languages Tail Recursion and Accumulators. Material adapted from Dan Grossman's PL class, U. Washington

Similar documents
Stack Allocation. Run-Time Data Structures. Static Structures

Stack machines The MIPS assembly language A simple source language Stack-machine implementation of the simple language Readings:

Lecture Outline. Stack machines The MIPS assembly language. Code Generation (I)

Module 2 Stacks and Queues: Abstract Data Types

COMP 356 Programming Language Structures Notes for Chapter 10 of Concepts of Programming Languages Implementing Subprograms.

Boolean Expressions, Conditions, Loops, and Enumerations. Precedence Rules (from highest to lowest priority)

CSc 372. Comparative Programming Languages. 21 : Haskell Accumulative Recursion. Department of Computer Science University of Arizona

Tail call elimination. Michel Schinz

Computer Systems Architecture

Chapter 15 Functional Programming Languages

Party Time! Computer Science I. Signature Examples PAIR OBJ. The Signature of a Procedure. Building a Set. Testing for Membership in a Set

Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson

In mathematics, it is often important to get a handle on the error term of an approximation. For instance, people will write

CSE341: Programming Languages Lecture 7.5 Course Motivation. Dan Grossman Fall 2011

Functional Programming. Functional Programming Languages. Chapter 14. Introduction

Recursion vs. Iteration Eliminating Recursion

16. Recursion. COMP 110 Prasun Dewan 1. Developing a Recursive Solution

4/1/2017. PS. Sequences and Series FROM 9.2 AND 9.3 IN THE BOOK AS WELL AS FROM OTHER SOURCES. TODAY IS NATIONAL MANATEE APPRECIATION DAY

What Is Recursion? 5/12/10 1. CMPSC 24: Lecture 13 Recursion. Lecture Plan. Divyakant Agrawal Department of Computer Science UC Santa Barbara

From Last Time: Remove (Delete) Operation

Chapter 5 Names, Bindings, Type Checking, and Scopes

MAX = 5 Current = 0 'This will declare an array with 5 elements. Inserting a Value onto the Stack (Push)

Linear Programming Notes VII Sensitivity Analysis

Organization of Programming Languages CS320/520N. Lecture 05. Razvan C. Bunescu School of Electrical Engineering and Computer Science

Coin Flip Questions. Suppose you flip a coin five times and write down the sequence of results, like HHHHH or HTTHT.

The Union-Find Problem Kruskal s algorithm for finding an MST presented us with a problem in data-structure design. As we looked at each edge,

Lecture 11: Tail Recursion; Continuations

CS412/CS413. Introduction to Compilers Tim Teitelbaum. Lecture 20: Stack Frames 7 March 08

Interpreters and virtual machines. Interpreters. Interpreters. Why interpreters? Tree-based interpreters. Text-based interpreters

language 1 (source) compiler language 2 (target) Figure 1: Compiling a program

What Is Recursion? Recursion. Binary search example postponed to end of lecture

Partial Fractions. Combining fractions over a common denominator is a familiar operation from algebra:

Chapter 5. Recursion. Data Structures and Algorithms in Java

Algorithm Design and Recursion

Time Limit: X Flags: -std=gnu99 -w -O2 -fomitframe-pointer. Time Limit: X. Flags: -std=c++0x -w -O2 -fomit-frame-pointer - lm

Coding Rules. Encoding the type of a function into the name (so-called Hungarian notation) is forbidden - it only confuses the programmer.

Semester Review. CSC 301, Fall 2015

10CS35: Data Structures Using C

6.090, IAP 2005 Lecture 8

Recursive Fibonacci and the Stack Frame. the Fibonacci function. The Fibonacci function defines the Fibonacci sequence, using a recursive definition :

Regions in a circle. 7 points 57 regions

High-Level Programming Languages. Nell Dale & John Lewis (adaptation by Michael Goldwasser)

Functional programming languages

How To Program In Scheme (Prolog)

Introduction to Data Structures

Advanced compiler construction. General course information. Teacher & assistant. Course goals. Evaluation. Grading scheme. Michel Schinz

Writing Functions in Scheme. Writing Functions in Scheme. Checking My Answer: Empty List. Checking My Answer: Empty List

The Advantages of Dan Grossman CSE303 Spring 2005, Lecture 25

Class Overview. CSE 326: Data Structures. Goals. Goals. Data Structures. Goals. Introduction

DATA STRUCTURES USING C

Roman Numerals Case Study 1996 M. J. Clancy and M. C. Linn

Compositional hardware virtualization. Raphael kena Poss University of Amsterdam January 11th, 2014

OAMulator. Online One Address Machine emulator and OAMPL compiler.

Compilers I - Chapter 4: Generating Better Code

Programming Languages in Artificial Intelligence

Functional Programming

Introduction to Python

CATIA V5 Surface Design

Basic Lisp Operations

Programming Languages. Session 5 Main Theme Functional Programming. Dr. Jean-Claude Franchitti

Data Structures and Algorithms

Common Data Structures

PROBLEMS (Cap. 4 - Istruzioni macchina)

Introduction to Programming System Design. CSCI 455x (4 Units)

Algorithms and Data S tructures Structures Stack, Queues, and Applications Applications Ulf Leser

Can interpreting be as fast as byte compiling? + Other developments in pqr

Pushdown Automata. place the input head on the leftmost input symbol. while symbol read = b and pile contains discs advance head remove disc from pile

Linked List Problems

Scheme Slides 101 by Paulo Jorge Matos May 4th,

Sequence of Numbers. Mun Chou, Fong QED Education Scientific Malaysia

Instruction Set Architecture (ISA)

Hadoop and Map-reduce computing

Elliott Wave Rules and Guidelines Overview of AGET Time and Price Squares Tool page 1

The Clean programming language. Group 25, Jingui Li, Daren Tuzi

Memory Allocation. Static Allocation. Dynamic Allocation. Memory Management. Dynamic Allocation. Dynamic Storage Allocation

PES Institute of Technology-BSC QUESTION BANK

7.1 Our Current Model

Basic Fibonacci Trading. Neal Hughes (FibMaster)

Ummmm! Definitely interested. She took the pen and pad out of my hand and constructed a third one for herself:

The previous chapter provided a definition of the semantics of a programming

Course: Programming II - Abstract Data Types. The ADT Stack. A stack. The ADT Stack and Recursion Slide Number 1

APP INVENTOR. Test Review

Parallelization: Binary Tree Traversal

Scoping (Readings 7.1,7.4,7.6) Parameter passing methods (7.5) Building symbol tables (7.6)

Functions Recursion. C++ functions. Declare/prototype. Define. Call. int myfunction (int ); int myfunction (int x){ int y = x*x; return y; }

Questions 1 through 25 are worth 2 points each. Choose one best answer for each.

Practical Online Filesystem Checking and Repair

INTERNATIONAL JOURNAL OF PURE AND APPLIED RESEARCH IN ENGINEERING AND TECHNOLOGY

Pseudo code Tutorial and Exercises Teacher s Version

Data Structures. Chapter 8

CS104: Data Structures and Object-Oriented Design (Fall 2013) October 24, 2013: Priority Queues Scribes: CS 104 Teaching Team

Machine-Code Generation for Functions

1) The postfix expression for the infix expression A+B*(C+D)/F+D*E is ABCD+*F/DE*++

Michelle Light, University of California, Irvine 10, August 31, The endangerment of trees

Cost Model: Work, Span and Parallelism. 1 The RAM model for sequential computation:

INVENTIONS. The main steps of the engineering design process are to: Define the Problem. Do Background Research. Possible Solutions.

Algorithms. Margaret M. Fleck. 18 October 2010

The Needle Programming Language

TOWARDS A GREEN PROGRAMMING PARADIGM FOR MOBILE SOFTWARE DEVELOPMENT

Data Structures. Algorithm Performance and Big O Analysis

Transcription:

Programming Languages Tail Recursion and Accumulators Material adapted from Dan Grossman's PL class, U. Washington

Recursion Should now be comfortable with recursion: No harder than using a loop (Maybe?) Often much easier than a loop When processing a tree (e.g., evaluate an arithmetic expression) Avoids mutation even for local variables Now: How to reason about efficiency of recursion The importance of tail recursion Using an accumulator to achieve tail recursion [No new language features here] 2

Call-stacks While a program runs, there is a call stack of function calls that have started but not yet returned Calling a function f pushes an instance of f on the stack When a call to f to finishes, it is popped from the stack These stack frames store information such as the values of arguments and local variables information about what is left to do in the function (further computations to do with results from other function calls) Due to recursion, multiple stack-frames may be calls to the same function 3

Example (define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) (fact 0) (fact 1) (fact 1) => (* 1 _) (fact 2) (fact 2) => (* 2 _) (fact 2) => (* 2 _) (fact 3) (fact 3) => (* 3 _) (fact 3) => (* 3 _) (fact 3) => (* 3 _) (fact 0) => 1 (fact 1) => (* 1 _) (fact 1) => (* 1 1) (fact 2) => (* 2 _) (fact 2) => (* 2 _) (fact 2) => (* 2 1) (fact 3) => (* 3 _) (fact 3) => (* 3 _) (fact 3) => (* 3 _) (fact 3) => (* 3 2) 4

What's being computed (fact 3)! => (* 3 (fact 2))! => (* 3 (* 2 (fact 1)))! => (* 3 (* 2 (* 1 (fact 0))))! => (* 3 (* 2 (* 1 1)))! => (* 3 (* 2 1))! => (* 3 2)! => 6! 5

Example Revised (define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1)) Still recursive, more complicated, but the result of recursive calls is the result for the caller (no remaining multiplication) 6

Example Revised (define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1)) (f2-h 2 3) (f2-h 1 6) (f2-h 2 3) => _ (f2-h 3 1) (f2-h 3 1) => _ (f2-h 3 1) => _ (fact2 3) (fact2 3) => _ (fact2 3) => _ (fact2 3) => _ (f2-h 0 6) (f2-h 0 6) => 6 (f2-h 1 6) => _ (f2-h 1 6) => _ (f2-h 1 6) => 6 (f2-h 2 3) => _ (f2-h 2 3) => _ (f2-h 2 3) => _ (f2-h 2 3) => 6 (f2-h 3 1) => _ (f2-h 3 1) => _ (f2-h 3 1) => _ (f2-h 3 1) => _ (fact2 3) => _ (fact2 3) => _ (fact2 3) => _ (fact2 3) => _ 7

What's being computed (fact2 3)! => (fact2-helper 3 1)! => (fact2-helper 2 3)! => (fact2-helper 1 6)! => (fact2-helper 0 6)! => 6! 9

An optimization It is unnecessary to keep around a stack-frame just so it can get a callee s result and return it without any further evaluation Racket recognizes these tail calls in the compiler and treats them differently: Pop the caller before the call, allowing callee to reuse the same stack space (Along with other optimizations,) as efficient as a loop (Reasonable to assume all functional-language implementations do tail-call optimization) includes Racket, Scheme, LISP, ML, Haskell, OCaml 10

What really happens (define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1)) (fact 3) (f2-h 3 1) (f2-h 2 3) (f2-h 1 6) (f2-h 0 6) 11

Moral Where reasonably elegant, feasible, and important, rewriting functions to be tail-recursive can be much more efficient Tail-recursive: recursive calls are tail-calls meaning all recursive calls must be the last thing the calling function does no additional computation with the result of the callee There is also a methodology to guide this transformation: Create a helper function that takes an accumulator Old base case's return value becomes initial accumulator value Final accumulator value becomes new base case return value 12

(define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) Old base case's return value becomes initial accumulator value. (define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1)) Final accumulator value becomes new base case return value. 13

Another example (define (sum1 lst) (if (null? lst) 0 (+ (car lst) (sum1 (cdr lst))))) (define (sum2 lst) (define (sum2-helper lst acc) (if (null? lst) acc (sum2-helper (cdr lst) (+ (car lst) acc)))) (sum2-helper lst 0)) 14

And another (define (rev1 lst) (if (null? lst) '() (append (rev1 (cdr lst)) (list (car lst))))) (define (rev2 lst) (define (rev2-helper lst acc) (if (null? lst) acc (rev2-helper (cdr lst) (cons (car lst) acc)))) (rev2-helper lst '())) 15

Actually much better (define (rev1 lst) ; Bad version (non T-R) (if (null? lst) '() (append (rev1 (cdr lst)) (list (car lst))))) For fact and sum, tail-recursion is faster but both ways linear time The non-tail recursive rev is quadratic because each recursive call uses append, which must traverse the first list And 1 + 2 + + (length-1) is almost length * length / 2 Moral: beware append, especially if 1 st argument is result of a recursive call cons is constant-time (and fast), so the accumulator version rocks 16

Tail-recursion == while loop with local variable (define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1)) def fact2(n): acc = 1 while n!= 0: acc = acc * n n = n 1 return acc 17

Tail-recursion == while loop with local variable (define (sum2 lst) (define (sum2-helper lst acc) (if (null? lst) acc (sum2-helper (cdr lst) (+ (car lst) acc)))) (sum2-helper lst 0)) def sum2(lst): acc = 0 while lst!= []: acc = lst[0] + acc lst = lst[1:] return acc 18

Tail-recursion == while loop with local variable (define (rev2 lst) (define (rev2-helper lst acc) (if (null? lst) acc (rev2-helper (cdr lst) (cons (car lst) acc)))) (rev2-helper lst '())) def rev2(lst): acc = [] while lst!= []: acc = [lst[0]] + acc lst = lst[1:] return acc 19

Always tail-recursive? There are certainly cases where recursive functions cannot be evaluated in a constant amount of space Example: functions that process trees Lists can be used to represent trees: '((1 2) ((3 4) 5)) 1 2 5 3 4 In these cases, the natural recursive approach is the way to go You could get one recursive call to be a tail call, but rarely worth the complication 20

Precise definition If the result of (f x) is the return value for the enclosing function body, then (f x) is a tail call i.e., don't have to do any more processing of (f x) to end function Can define this notion more precisely A tail call is a function call in tail position The single expression (ignoring nested defines) of the body of a function is in tail position. If (if test e1 e2) is in tail position, then e1 and e2 are in tail position (but test is not). (Similar for cond-expressions) If a let-expression is in tail position, then the single expression of the body of the let is in tail position (but no variable bindings are) Arguments to a function call are not in tail position 21

Are these functions tail-recursive? (define (get-nth lst n)!! (if (= n 0) (car lst)! (get-nth (cdr lst) (- n 1))))! (define (good-max lst)!! (cond! ((null? (cdr lst))! (car lst))! (#t! (let ((max-of-cdr (good-max (cdr lst))))! (if (> (car lst) max-of-cdr)! (car lst) max-of-cdr)))))! 22

Try these Write a tail-recursive max function (i.e., a function that returns the largest element in a list). Write a tail-recursive Fibonacci sequence function (i.e., a function that returns the n'th number of the Fibonacci sequence). (fib 1) => 1 (fib 2) => 1 (fib 3) => 2 (fib 4) => 3 (fib 5) => 5 In general, (fib n) = (+ (fib (- n 1)) (fib (-n 2))) 23

(define (maxtr lst)! (define (maxtr-helper lst max-so-far)! (cond ((null? lst) max-so-far)! ((> max-so-far (car lst))! (maxtr-helper (cdr lst) max-so-far))! (#t (maxtr-helper (cdr lst) (car lst)))))! (maxtr-helper (cdr lst) (car lst)))! 24

(define (fib-tr n)!!! (define (fib-helper n-minus-1 n-minus-2 current-n)! (if (= current-n n)! (+ n-minus-1 n-minus-2)! (fib-helper (+ n-minus-1 n-minus-2)! n-minus-1! (+ 1 current-n))))! (if (< n 3) 1 (fib-helper 1 1 3)))! 25