On this page:
12.1 Introduction
12.2 Optional Reading
12.3 Part 1:   Language Comparison
12.4 Part 2:   Implementation
12.5 Grammar
12.6 Implementation Hints
12.7 Starter Code
12.8 What To Hand In
12 Generators

    12.1 Introduction

    12.2 Optional Reading

    12.3 Part 1: Language Comparison

    12.4 Part 2: Implementation

    12.5 Grammar

    12.6 Implementation Hints

    12.7 Starter Code

    12.8 What To Hand In

12.1 Introduction

In this assignment, you will implement generators.

12.2 Optional Reading

This article provides a very nice introduction to generators in the Icon programming language, which is no longer used but inspired most modern work on generators. Generators in Icon were central to the language, not an added-on feature, and this shows how they are integrated into the language. (Notice, in paricular, how the truthy-falsy nature of the language appears to be a win early on, but becomes problematic later on.) You can see other examples on Wikipedia. It’s worth noting that Icon was created in the late 1970s!
You do not need to read this article for anything in this assignment. However, it’s important if you want to further study programming languages. You may also find it interesting for thinking as a programmer.

12.3 Part 1: Language Comparison

Consider the following program written in Python 3 (run it online!):

x = 0

 

def a(b):

    global x

    b()

    x = 3

 

def c():

    a(lambda: (yield 1))

    yield x

 

gen = c()

print(next(gen))

print(next(gen))

We’ve translated the above program into this seemingly equivalent Racket program (run it online!):
#lang racket
(require racket/generator)
 
(define x 0)
 
(define (a b)
 (b)
 (set! x 3))
 
(define c
 (generator ()
            (a (lambda () (yield 1)))
            (yield x)))
 
(c)
(c)
First, confirm that these programs look equivalent; that is, we might expect the generators in both to yield 1, then yield 3. Then, run both programs, and answer the following question:
Do these programs behave the same way? If not, try to explain why they differ. You are welcome to consult the Internet for help understanding their behaviors.

12.4 Part 2: Implementation

You will now emulate Python 3 generators in Racket by adapting your aci implementation from the ACI assignment to implement the generator construct:
#lang racket
(generator (arg-name) body)
which defines an unary function. This function returns a generator with routine body, and any references to arg-name within body are bound to the function’s argument.

Be very careful when you’re reading this spec—the generator construct defines a function, not a generator. Applying the function defined by generator (the syntactic construct) produces a generator (in the semantic sense).

In addition to the base constructs from the aci language (that is, everything except web-read), the language for a generator’s body should support the following new forms:
  • (yield exp)
    which yields the value of exp from the generator. We need our aci implementation because of yieldaci provides the continuation needed to store the generator’s current state before yielding.
    (yield ...) itself should evaluate to (void). Also, programs may nest yields, and inner yields should yield before outer yields.

  • (loop exp)
    which infinitely runs exp. (We don’t need loop to understand generators, but it makes it easier to write interesting programs [e.g., streams].)

Additionally, your generator should not allow the use of yield or loop inside of lambda - think about the provided python example for a clue about why we might want to disallow such uses of yield and loop. Any programs that attempt to do either thing should result in an error (a syntax error is acceptable here).
The body of a generator should be able to support references to definitions defined outside of the body (for example, functions and variables previously declared in a define statement), though those outer definitions do not need to support the constructs in the generator language (such as yield and loop). To do this, you should update your aci macro such that function applications do not pass their current continuation to the callee. Instead, the function application’s result should be given to the continuation just as with atomic values.

This modification to how continuations interact with function applications matches Python’s semantics.

Finally, you should implement the next function, which takes a generator as an argument, resumes computation of the generator from the point at which it last yielded, and returns the next value that the generator yields. If the generator has reached the end of its body, next raises an error. If next is called on a previously exhausted generator (one that has already caused next to error) its behavior is undefined. If the generator never reaches the end of its body and never reaches a yield, next may loop infinitely.

12.5 Grammar

Your final generator macro implementation should handle the following grammar:

<generator> ::= (generator (<id>) <gen-expr>)

 

<gen-expr> ::= <basic-expr>

             | (yield <gen-expr>)

             | (loop <gen-expr>)

 

<basic-expr> ::= <num>

               | <string>

               | true

               | false

               | (<gen-expr> <gen-expr>)       # function application

               | (lambda (<id>) <basic-expr>)

               | (set! <id> <gen-expr>)

               | (begin <gen-expr> <gen-expr>)

               | (+ <gen-expr> <gen-expr>)

               | (string-append <gen-expr> <gen-expr>)

12.6 Implementation Hints
12.7 Starter Code

We’ve provided starter code for your implementation at generators.rkt. This includes a template for your generator macro as well as the next function.
We’ve also provided a stencil for your test cases at generators-tests.rkt and testing support code at test-support.rkt. You should check that you can run your type-generators-tests.rkt file successfully in DrRacket before submitting—if you can’t, it means that a definition is missing or you’re trying to test a function that you shouldn’t be testing.

12.8 What To Hand In

You will submit two files for this assignment:
  • generators.rkt, which should be uploaded to the “Code” drop on Gradescope.

  • generators-tests.rkt, which should be uploaded to the “Tests” drop on Gradescope.

Finally, submit your answer to Part 1: Language Comparison to the “Question” drop on Gradescope.
You can update your submissions as many times as you want before the deadline.