Homework 7:
Syntactic Abstractions

Due: Monday, March 25, at 11:59 PM

Introduction

For this assignment, you will write a function to translate a syntactic abstraction into its core form, a few functions to implement a new data type, and one function to do static analysis of programs in our little language. The assignment should help you begin to feel more comfortable with the idea of syntactic abstraction and get more practice working with the little language.

Template Source Files

Download the zipped directory hw07.zip. It contains:

Please use the given names for the files, and do not modify the provide or require clauses in either file.

With provide, you must define all five functions. If you don't have time to solve a problem, define a function that takes the correct number of arguments and returns a legal default value, such as 0 or '().

Organizing Code

As before, you will extend homework07.rkt with the code you write for this assignment. Be sure to update the header block with your personal information. Put any helper functions you write for a problem after the main function of your solution.

You do not have to write Rackunit tests for the functions you write. However, I strongly encourage you to evaluate your code using the examples I give in the assignment and to think of other cases that test your functions.

The Little Language

For Problem 5, you will work the little language we saw for the first time in Session 12, processed in Session 13, and extended in Session 16.

<exp> ::= <varref>                      ; core
        | (lambda (<var>) <exp>)        ; core
        | (<exp> <exp>)                 ; core
        | (let (<exp> <exp>) <exp>)

The core language consists of variable references, lambda expressions, and applications. The full language contains the core features plus local variables ("let" expressions), which are a syntactic abstraction.

Note that any function that processes an expression in the core language should consider only three cases: variable references, lambda expressions, and applications.

A Set Data Type

When doing static analysis, we sometimes need to keep track of a set of symbols. For example, we might want to determine the set of all variable references in a program.

We can use a Racket list to implement a set as a list with no duplicates. The order of the items in a set does not matter.

<set-of-symbols> ::= ()
                   | (<item> . <set-of-symbols>)

Using a Racket list of symbols provides us with a lightweight implementation of sets. All we need to do is to implement set operations as functions that process lists of symbols in a way that preserve the meaning of those operations for sets. You will write five such functions for Problems 2,3, and 4, to build sets and test membership.

Problems

  1. Write a structurally recursive function named (curry exp) that takes one argument, a Racket lambda expression with n ≥ 1 formal parameters. curry returns a curried function.

    For example:
    > (curry '(lambda (x) anything))
    '(lambda (x) anything)
    
    > (curry '(lambda (x y z) anything))
    '(lambda (x)
      (lambda (y)
        (lambda (z)
          anything)))
    
    curry does not process the function body, which can be any Racket expression.

    Hint: Make curry an interface procedure that passes the parameter list and the body to a structurally recursive helper function that processes the parameter list, which is a list of symbols. Note: you do not need to pass the symbol lambda to the helper! That is a constant the helper can insert on its own.

  2. Define three short non-recursive functions that implement basic set membership for the set datatype described above:
    • (empty-set), which takes no arguments and returns an empty set as its value. In our implementation, an empty set is simply an empty list. (Yes, this is a simple function!)
    • (set-member? sym S), which takes two arguments, a symbol sym and a set of symbols S. set-member? returns true if sym is in S, and false otherwise.
    • (set-add sym S), which takes two arguments, a symbol sym and a set of symbols S. set-add returns a set containing sym and all of the symbols in S, with no duplicates.
    For example:
    > (empty-set)
    '()
    > (set-member? 'a (empty-set))
    #f
    > (set-member? 'a '(x y z))
    #f
    > (set-member? 'y '(x y z))
    #t
    > (set-add 'a '(x y z))
    '(a x y z)
    > (set-add 'y '(x y z))
    '(x y z)
    
    You will find the primitive function member helpful for implementing both of these functions:
    • It does the same task as set-member?, except that it returns a list when it finds the item. set-member? always returns #t or #f.
    • In set-add, you can use it to determine if S already contains sym before adding it with cons.

  3. Write the recursive constructor function (set-union S1 S2), which takes as arguments two sets of symbols, S1 and S2. set-union returns a set containing all of the items in both S1 and S2, with no duplicates.

    For example:
    > (set-union '(a b) '(x y z))
    '(a b x y z)
    
    > (set-union '(a x y z b) '(x y z))
    '(a b x y z)
    
    To implement set-union, use set-add from Problem 2 to recursively add each item from S1 to S2.

  4. Write the recursive membership function (set-subset? S1 S2), which takes as arguments two sets of symbols, S1 and S2. set-subset? returns true if every member of S1 is in S2, and false otherwise.

    For example:
    > (set-subset? '(a b) '(x y z))
    #f
    
    > (set-subset? '(x y z) '(a x y z b))
    #t
    
    To implement set-subset?, use set-member? from Problem 2 to recursively check to see if each item of S1 is a member of S2.

  5. Write a structurally recursive function named (free-vars exp) that takes as input an expression in the core little language, and returns a set of all of the variables that occur free in expression.

    For example:
    > (free-vars 'x)
    '(x)
    > (free-vars '(square x))
    '(square x)
    > (free-vars '(lambda (y) (x y)))
    '(x)
    > (free-vars '((lambda (y) (y (square x)))
                   (lambda (y) (f f))))
    '(square x f)
    > (free-vars (preprocess '(let (a b)
                                (let (c (lambda (d) a))
                                  (c a)))))
    '(b)
    
    Use the functions you wrote for Problems 2 and 3 in your solution, whenever you need to create an empty set, add an item to a set, or find the union two sets.

    You will probably also want to use Racket's primitive remove function. It removes the first occurrence of an item from a list. Because the lists we pass it here are sets, removing the first occurrence of a symbol removes the only occurrence!

    As always, you must use the same syntax procedures for the little language. I have added syntax procedures for the new let expression and updated the exp? type predicate.

Deliverables

By the due time and date, use the course submission system to submit the following files electronically:

Be sure that your submission follows the submission requirements. Be sure to use the specified name for your file. This enables the autograder to find and run your code.