TAIL CALLS, ITERATORS, AND GENERATORS 11

Similar documents
ITERATORS AND GENERATORS 10

Delayed Expressions Fall 2017 Discussion 9: November 8, Iterables and Iterators. For Loops. Other Iterable Uses

SCHEME 10 COMPUTER SCIENCE 61A. July 26, Warm Up: Conditional Expressions. 1. What does Scheme print? scm> (if (or #t (/ 1 0)) 1 (/ 1 0))

INTERPRETERS 8. 1 Calculator COMPUTER SCIENCE 61A. November 3, 2016

ITERATORS AND STREAMS 9

Summer 2017 Discussion 10: July 25, Introduction. 2 Primitives and Define

INTERPRETERS AND TAIL CALLS 9

Interpreters and Tail Calls Fall 2017 Discussion 8: November 1, 2017 Solutions. 1 Calculator. calc> (+ 2 2) 4

61A LECTURE 22 TAIL CALLS, ITERATORS. Steven Tang and Eric Tzeng July 30, 2013

Fall 2017 December 4, Scheme. Instructions

Fall 2017 December 4, 2017

CS 61A Interpreters, Tail Calls, Macros, Streams, Iterators. Spring 2019 Guerrilla Section 5: April 20, Interpreters.

Fall 2018 Discussion 8: October 24, 2018 Solutions. 1 Introduction. 2 Primitives

Fall 2017 Discussion 7: October 25, 2017 Solutions. 1 Introduction. 2 Primitives

TAIL RECURSION, SCOPE, AND PROJECT 4 11

Advanced topics, part 2

CS61A Lecture 36. Soumya Basu UC Berkeley April 15, 2013

ENVIRONMENT DIAGRAMS AND RECURSION 2

Structure and Interpretation of Computer Programs

ITERATORS, GENERATORS, GENERIC FUNCTIONS

ITERATORS, GENERATORS, GENERIC FUNCTIONS

SCHEME 8. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. March 23, 2017

SCHEME 7. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. October 29, 2015

Streams. CS21b: Structure and Interpretation of Computer Programs Spring Term, 2004

CS450 - Structure of Higher Level Languages

Resources matter. Orders of Growth of Processes. R(n)= (n 2 ) Orders of growth of processes. Partial trace for (ifact 4) Partial trace for (fact 4)

Spring 2018 Discussion 7: March 21, Introduction. 2 Primitives

Iterators & Generators

SCHEME AND CALCULATOR 5b

6.001 Notes: Section 4.1

CSE 341 Lecture 5. efficiency issues; tail recursion; print Ullman ; 4.1. slides created by Marty Stepp

Module 05: Types of recursion

Streams and Evalutation Strategies

CSCC24 Functional Programming Scheme Part 2

An introduction to Scheme

Introduction to Functional Programming

Class 6: Efficiency in Scheme

RECURSION, RECURSION, (TREE) RECURSION! 2

Computer Science 21b (Spring Term, 2015) Structure and Interpretation of Computer Programs. Lexical addressing

CS116 - Module 5 - Accumulative Recursion

Homework 1. Notes. What To Turn In. Unix Accounts. Reading. Handout 3 CSCI 334: Spring, 2017

Macros & Streams Spring 2018 Discussion 9: April 11, Macros

MORE SCHEME. 1 What Would Scheme Print? COMPUTER SCIENCE MENTORS 61A. October 30 to November 3, Solution: Solutions begin on the following page.

RECURSION 3. 1 Recursion COMPUTER SCIENCE 61A. June 30, 2016

COSE212: Programming Languages. Lecture 4 Recursive and Higher-Order Programming

(Python) Chapter 3: Repetition

RECURSION, RECURSION, (TREE) RECURSION! 3

An Explicit-Continuation Metacircular Evaluator

CS1 Recitation. Week 2

Homework 1. Reading. Problems. Handout 3 CSCI 334: Spring, 2012

SCHEME The Scheme Interpreter. 2 Primitives COMPUTER SCIENCE 61A. October 29th, 2012

CircuitPython 101: Working with Lists, Iterators and Generators

CSC324 Functional Programming Efficiency Issues, Parameter Lists

15-122: Principles of Imperative Computation, Fall 2015

Algorithm Design and Recursion. Search and Sort Algorithms

Hints for Exercise 4: Recursion

Intro. Scheme Basics. scm> 5 5. scm>

6.01, Spring Semester, 2008 Assignment 3, Issued: Tuesday, February 19 1

Functional Programming in Haskell for A level teachers

RECURSION, RECURSION, (TREE) RECURSION! 3


Functional Programming. Pure Functional Programming

Deferred operations. Continuations Structure and Interpretation of Computer Programs. Tail recursion in action.

RECURSION 7. 1 Recursion COMPUTER SCIENCE 61A. October 15, 2012

TUPLES AND RECURSIVE LISTS 5

Week - 03 Lecture - 18 Recursion. For the last lecture of this week, we will look at recursive functions. (Refer Slide Time: 00:05)

Structure and Interpretation of Computer Programs

RECURSION: n. SEE RECURSION 3

Python iterators and generators

Chapter 15. Functional Programming Languages

Faced with the choice between changing one s mind and proving that there is no need to do so, almost everyone gets busy on the proof.

Generative and accumulative recursion. What is generative recursion? Example revisited: GCD. Readings: Sections 25, 26, 27, 30, 31

CPS 506 Comparative Programming Languages. Programming Language Paradigm

Midterm Exam 2B Answer key

by the evening of Tuesday, Feb 6

Module 10: Imperative Programming, Modularization, and The Future

Discussion 2C Notes (Week 5, February 4) TA: Brian Choi Section Webpage:

Containers Blocks and Iterators in Ruby

Functional Programming. Pure Functional Languages

Code example: sfact SICP Explicit-control evaluator. Example register machine: instructions. Goal: a tail-recursive evaluator.

CS 135 Winter 2018 Tutorial 7: Accumulative Recursion and Binary Trees. CS 135 Winter 2018 Tutorial 7: Accumulative Recursion and Binary Trees 1

Structure and Interpretation of Computer Programs Summer 2014 Midterm 2

CISC-124. Casting. // this would fail because we can t assign a double value to an int // variable

CS 61A Discussion 8: Scheme. March 23, 2017

Recursion & Iteration

AXIOMS FOR THE INTEGERS

More About Recursive Data Types

a function that calls itself It doesn t do anything! Defining a recursive function 1) divide and conquer 2) base case 6/21/2018 chapter 15

CONCEPTS OF PROGRAMMING LANGUAGES Solutions for Mid-Term Examination

ITERATION AND RECURSION 3

CS115 INTRODUCTION TO COMPUTER SCIENCE 1. Additional Notes Module 5

Tail Recursion. ;; a recursive program for factorial (define fact (lambda (m) ;; m is non-negative (if (= m 0) 1 (* m (fact (- m 1))))))

Repetition Through Recursion

CSC-140 Assignment 5

Getting Started with Haskell (10 points)

RECURSION AND LINKED LISTS 3

What is an algorithm?

The Art of Recursion: Problem Set 10

Environment Diagrams and Recursion Fall 2017 Discussion 2: September 6, 2017 Solutions. 1 More Environment Diagrams

Unit #2: Recursion, Induction, and Loop Invariants

Transcription:

TAIL CALLS, ITERATORS, AND GENERATORS 11 COMPUTER SCIENCE 61A November 13, 214 1 Tail Calls Scheme implements tail-call optimization, which allows programmers to write recursive functions that use a constant amount of space. A tail call occurs when a function calls another function as its last action. As the tail call is the last action for the current frame, Scheme won t make any further variable lookups in the frame. Therefore, the frame is no longer needed, and we can remove it from memory. Consider this version of factorial that does not use tail calls: (define (fact n) (if (= n ) 1 (* n (fact (- n 1))))) The recursive call occurs in the last line, but it is not the last expression evaluated. After calling (fact (- n 1)), the function still needs to multiply that result with n. The final expression that is evaluated is a call to the multiplication function, not fact itself. Therefore, the recursive call is not a tail call. However, we can rewrite this function using a helper function that remembers the temporary product that we have calculated so far in each recursive step. (define (fact n) (define (fact-iter n prod) (if (= n ) prod (fact-iter (- n 1) (* n prod)))) (fact-iter n 1)) fact-iter makes single recursive call that is the last expression to be evaluated, so it is a tail call. Therefore, fact-iter is a tail recursive process. Tail recursive processes can 1

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 2 take a constant amount of memory because each recursive call frame does not need to be saved. Our original implementation of fact required the program to keep each frame open because the last expression multiplies the recursive result with n. Therefore, at each frame, we need to remember the current value of n. In contrast, the tail recursive fact-iter does not require the interpreter to remember the values for n or prod in each frame. Once a new recursive frame is created, the old one can be dropped, as all the information needed is included in the new frame. Therefore, we can carry out the calculation using only enough memory for a single frame. 1.1 Identifying tail calls A function call is a tail call if it is in a tail context. We consider the following to be tail contexts: the last sub-expression in a lambda s body the second or third sub-expression in an if form any of the non-predicate sub-expressions in a cond form the last sub-expression in an and or an or form the last sub-expression in a begin s body 1.2 Questions 1. For each of the following functions, identify whether it contains a recursive tail call. Also indicate if it uses a constant number of frames. (define (question-a x) (if (= x ) (+ x (question-a (- x 1))))) (define (question-b x y) (if (= x ) y (question-b (- x 1) (+ y x))))

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 3 (define (question-c x y) (if (> x y) (question-c (- y 1) x) (question-c (+ x 1) y))) (define (question-d n) (if (question-d n) (question-d (- n 1)) (question-d (+ n 1)))) 1.3 Extra Questions 1. Write a tail recursive function that returns the nth fibonacci number. We define fib() = and fib(1) = 1. (define (fib n) 2. Write a tail recursive function, reverse, that takes in a Scheme list and returns a reversed copy. (define (reverse lst)

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 4 2 Iterators An iterator is an object that represents a sequence of values. Here is an example of a class that implements Python s iterator interface. This iterator calculates all of the natural numbers one-by-one, starting from zero: class Naturals(): def init (self): self.current = def next (self): result = self.current self.current += 1 return result def iter (self): return self There are two components of Python s iterator interface: the next method, and the iter method. 2.1 next The next method usually does two things: 1. calculates the next value 2. checks if it has any values left to compute To return the next value in the sequence, the iterator does some computation defined in the next method. When there are no more values left to compute, the next method must raise a type of exception called StopIteration. This signals the end of the sequence. Note: the next method defined above does NOT raise any StopIteration exceptions. Why? Because there are always more values left to compute! Remember, there is no last natural number, so there is technically no end of the sequence. However, if you wanted to define a finite iterator, then you would raise a StopIteration after returning the final value. 2.2 iter The purpose of the iter method is to return an iterator object. By definition, an iterator object is an object that has implemented both the next and iter methods.

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 5 This has an interesting consequence. If a class implements both a next method and a iter method, its iter method can just return self (like in the example). Since the class implements both next and iter, it is technically an iterator object, so its iter method can just return itself. 2.3 Implementation When defining an iterator object, you should always keep track of how much of the sequence has already been computed. In the above example, we use an instance variable self.current to keep track. Iterator objects maintain state. Successive calls to next will most likely output different values each time, so next is considered non-pure. How do we call next and iter? Python has built-in functions called next and iter for this. Calling next(some iterator) will then cause Python to implicitly call some iterator s next method. Calling iter(some_iterator) will make a similar implicit call to some_iterator s iter method. For example, this is how we would use the Naturals iterator: >>> nats = Naturals() >>> nats_iter = iter(nats) 1 2 However, we don t really need to call iter on nats. Why not? Because you can use iterator objects in for loops. In other words, any object that satisfies the iterator interface can be iterated over: >>> nats = Naturals() >>> for n in nats: print(n) 1 2... # Forever! This works because the Python for loop implicitly calls the iter method of the object being iterated over, and repeatedly calls next on it. In other words, the above interaction is (basically) equivalent to:

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 6 nats_iter = iter(nats) is_done = False while not is_done: try: val = next(nats_iter) print(val) except StopIteration: is_done = True 2.4 Questions 1. Define an iterator whose i-th element is the result of combining the i-th elements of two input iterables using some binary operator, also given as input. The resulting iterator should have a size equal to the size of the shorter of its two input iterators. >>> from operator import add >>> evens = IterCombiner(Naturals(), Naturals(), add) >>> next(evens) >>> next(evens) 2 >>> next(evens) 4 class IterCombiner(object): def init (self, iter1, iter2, combiner): def next (self): def iter (self):

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 7 2. What is the result of executing this sequence of commands? >>> naturals = Naturals() >>> doubled_naturals = IterCombiner(naturals, naturals, add) >>> next(doubled_naturals) >>> next(doubled_naturals) 2.5 Extra Practice 1. Create an iterator that generates the sequence of Fibonacci numbers. class Fibonacci(object): def init (self): def next (self): def iter (self): 3 Generators A generator function is a special kind of Python function that uses a yield statement instead of a return statement to report values. When a generator function is called, it returns an iterable object. Here is an iterator for the natural numbers written using the generator construct:

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 8 def generate_naturals(): current = while True: yield current current += 1 Calling generate naturals() will return a generator object: >>> gen = generate_naturals() >>> gen <generator object gen at...> To use the generator object, you then call next on it: >>> next(gen) >>> next(gen) 1 >>> next(gen) 2 Think of a generator object as containing an implicit next method. This means, by definition, a generator object is an iterator. 3.1 yield The yield statement is similar to a return statement. However, while a return statement closes the current frame after the function exits, a yield statement causes the frame to be saved until the next time next is called, which allows the generator to automatically keep track of the iteration state. Once next is called again, execution picks up from where the previously executed yield statement left off, and continues until the next yield statement (or the end of the function) is encountered. Including a yield statement in a function automatically signals to Python that this function will create a generator. When we call the function, it will return a generator object, instead of executing the code inside the body. When the returned generator s next method is called, the code in the body is executed for the first time, and stops executing upon reaching the first yield statement. 3.2 Implementation Because generators are technically iterators, you can implement iter methods using only generators. For example,

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 9 class Naturals(): def init (self): self.current = def iter (self): while True: yield self.current self.current += 1 Naturals iter method now returns a generator object. The usage of a Naturals object is exactly the same as before: >>> nats = Naturals() >>> nats_iter = iter(nats) 1 2 There are a couple of things to note: No next method in Naturals. Remember, iter only needs to return an object that has implemented a next method. Since generators have their own next method, the new Naturals implementation is perfectly valid. nats is a Naturals object and nats iter is a generator Since generators are iterators, you can also use generators in for loops. 3.3 Questions 1. Define a generator that yields the sequence of perfect squares. def perfect_squares():

DISCUSSION 11: TAIL CALLS, ITERATORS, AND GENERATORS Page 1 3.4 Extra Practice 1. Write a generator function that returns lists of all subsets of the positive integers from 1 to n. Each call to this generator s next method will return a list of subsets of the set [1, 2,..., n], where n is the number of times next was previously called. def generate_subsets(): """ >>> subsets = generate_subsets() >>> next(subsets) [[]] >>> next(subsets) [[], [1]] >>> next(subsets) [[], [1], [2], [1, 2]] """