Sample Midterm 4

Date: Tuesday, February 16th

 
Name:

Administrative

This is a sample midterm that was given in the 2009 fall semester.

Note about Typed Scheme: make sure that you write proper type declarations for functions that you’re writing. Also, if you use internal definitions then write type declarations for them. Grading will not be strict about the syntax of type declarations, but we will be looking at the types as well as the definitions. Also, in some of the given examples there are type annotations that are missing for the code to work. This should not be a problem for understanding the questions.

Please write clearly, especially in “essay questions”.

Question Grade  
(1) Simple Scheme programming /8
(2) Higher-Order Functions /18
(3) BNF Grammar /15
(4) Fill in the blanks /15
(5) Structural Recursion with ASTs /15
(6) Language Extension: Extending the FLANG language /18
(7) Language Features /18
(8) Schlac /18
Total /125
 


 

Question 1: Simple Scheme programming

 8 pts 

Write an add-between function that takes a list and an item, and produces the elements of the list with the item between each two elements. Here are some tests:

(test (add-between '(1 2 3) 9) => '(1 9 2 9 3)) (test (add-between '(1 2) 9) => '(1 9 2)) (test (add-between '(1) 9) => '(1)) (test (add-between '() 9) => '())
Remember to write a purpose statement and a correct type declaration.



 

Question 2: Higher-Order Functions

 18 pts 

  1. Write a higher-order flip function that consumes a binary function of any type, and returns a similar function that uses it’s arguments in reverse. For example (flip cons) is a function that takes a list and an item and conses the item onto the list.
    Tests:
    (define snoc (flip cons)) (test (snoc '(2 3) 1) => '(1 2 3)) (test ((flip -) 1 2) => 1)
    Again, remember to write a proper type declaration and a brief purpose statement.
    Note: In general you need to use lambda: to create functions in typed scheme, but you can ignore this and just use plain lambda here.
  2. Using flip from the first part of this question, and curry and map that we’ve seen in class, we can define the following function in a “point-free style” (similar to what we’ve seen in Lecture #9):
    (define foo ((currify map) ((currify (flip -)) 5)))
    Describe what this function is doing.
    (Note: this is a real function! We’re defining it using “point-free style”, which means that we’re combining other functions to create a new function with no explicit lambda, but it is still a function.)
    Do this in three ways: Reminder — the following is the definition of our currify from class, and the type of simple uses of map.
    (: currify : (All (A B C) ((A B -> C) -> (A -> (B -> C))))) ;; convert a double-argument function to a curried one (define (currify f) (lambda (x) (lambda (y) (f x y)))) (: map : (All (A B) ((A -> B) (Listof A) -> (Listof B))))


 

Question 3: BNF Grammar

 15 pts 

As we’ve seen in class, it is easy to change our parser to parse fully parenthesized infix expressions. Here is a new grammar modified this way:

<FLANG> ::= <num> | { <FLANG> + <FLANG> } | { <FLANG> - <FLANG> } | { <FLANG> * <FLANG> } | { <FLANG> / <FLANG> } | { with { <id> <FLANG> } <FLANG> } | <id> | { fun { <id> } <FLANG> } | { call <FLANG> <FLANG> }
There is a technical reason that we don’t do this in FLANG though. Specifically, the grammar becomes ambiguous. Demonstrate this by writing a FLANG expression that can evaluate in two different ways, to two different values. That is, the ambiguity should result in the expression having two different numerical results based on how it is parsed — neither way should lead to a parsing error (or any other kind of error).

Huge hint: an easy solution involves two nested withs, with bindings for + and for call.
(And rememebr – this is a FLANG expression, not Scheme code.)



 

Question 4: Fill in the blanks

 15 pts 

For each one of the following expressions, write a definition for blank in Scheme that makes the expression evaluate to 660. For example, a correct answer for this expression:
(* (blank) 2)
is:
(define blank (lambda () 330))
If you think that it is impossible to write such a definition, explain why this is the case. (Remember: this is Scheme, not Schlac or FLANG, or any other language that we’ve seen.)
You do not need to write types for definitions, except when the question asks for it.

Do this for these expressions:
  1. (blank +)
  2. (sqrt blank) ; hint: you do not need a calculator for this
  3. (car (map (blank) ’(660)))
  4. (if blank (blank) blank)
  5. ((lambda (blank blank) (+ blank blank)) 330 330) ; careful!


 

Question 5: Structural Recursion with ASTs

 15 pts 

Note: for this question refer to the FLANG-ENV interpreter — the FLANG implementation that uses environments.

In our implementation of FLANG using environments, the closures that we create close over the whole environment at the point of evaluating a fun expression. However, in many cases there is no need to close over the whole environment — only over identifiers that appear free in the body of the function. For example, when we get to evaluate the fun form in:

{with {x 1} {with {y 2} {fun {z} {+ z x}}}}
then we need to close only over xy does not appear free in the body of the function, so we don’t need to keep it around. Another example:
{with {x 1} {with {y 2} {fun {x} {with {y 3} {+ x y}}}}}
Here we don’t need to close over anything, because neither x nor y appear free in the body of the function.

We can implement this by introducing a new preprocessing step into the evaluator, which translates all Fun expressions into annotated AFun expressions — those will be just like Fun, except that they will have an extra field that will keep the list of free identifiers in the body. Then, when we get to evaluation, we can make AFun forms evaluate to FunV closures holding only the relevant bindings. In this question, you will implement this annotation step.

The new FLANG type definition is:

(define-type FLANG [Num (n Number)] [Add (lhs FLANG) (rhs FLANG)] [Sub (lhs FLANG) (rhs FLANG)] [Mul (lhs FLANG) (rhs FLANG)] [Div (lhs FLANG) (rhs FLANG)] [Id (name Symbol)] [With (name Symbol) (named FLANG) (body FLANG)] [Fun (name Symbol) (body FLANG)] [AFun (name Symbol) (body FLANG) ; <-- new variant (free-names (Listof Symbol))] ; <-- with a new field [Call (fun-expr FLANG) (arg-expr FLANG)])
Note that this is a hack for making things a bit simpler — a better solution would be to have a second type that has all of the usual AST entries except for Fun, and with AFun added.

Your annotator function should look like this:

(: annotate : FLANG -> FLANG) ;; annotate all `Fun' expressions by turning them into `AFun's ;; with a list of identifiers that appear free in the body. (define (annotate expr) (cases expr —«fill-in»—))

Hint: in question 5 of the second sample midterm there is a definition of a free-vars function — this is doing something very similar to what you need here: you should use it as a helper function.

Note: because of the hack of adding AFun to the existing AST definition, the annotator will need to also have a case for an AFun input. However, then should never happen, so you can make that case just thrown an “internal error”.



 

Question 6: Language Extension: Extending the FLANG language

 18 pts 

In this question you will implement the evaluation part of the last question. To do this, we first change the run function to annotate the code before it is evaluated:

(: run : String -> Number) ;; evaluate a FLANG program contained in a string (define (run str) (let ([result (eval (annotate (parse str)) (EmptyEnv))]) (cases result [(NumV n) n] [else (error 'run "evaluation returned a non-number: ~s" result)])))

Then, you need to:

The new code for AFun should be similar to the previous Fun case, except that now you will need to use the free-names field to filter the environment.

While you’re at it, further optimize the portion of the environment that is closed over by not keeping duplicate identifiers. For example, when this code:

{with {x 1} {with {x 2} {fun {y} x}}}
is evaluated, the closure’s environment should have a single x entry, where it is bound to 2 (not to 1!).

It will be easier to do this if you write a small utility function to do the filtering. (Note: if you know about filter — it could have been used, but when you scan the list you need to remember bindings that you’re scanning (to remove duplicates), and if you use filter then you won’t be able to do that — in any case, the environment in question is not kept as a list...)



 

Question 7: Language Features

 18 pts 

  1. Using our annotator step in the last two questions, you can make the following observation: if all functions in a program don’t close over any identifiers, then the program will run the same whether the interpreter uses dynamic or lexical scope.
  2. In this code from the ALGAE2 solution:
    (: And : (Listof ALGAE) -> ALGAE) ;; Translates `{and E ...}' syntax to core Algae. (define (And exprs) (cond [(null? exprs) (Bool #t)] [(null? (rest exprs)) (first exprs)] [else (If (car exprs) (And (cdr exprs)) (Bool #f))]))
    it looks like the second case is not really needed. Explain why this is not true. Hint: removing it still makes and evaluate to the same result when taken as a boolean, but destroying a certain feature of the language. (Removing the second case will still leave and as a form that returns the same boolean answers when used with booleans — it is some other feature that is destroyed.)
    To demonstrate this, write some Scheme code using and, which would run differently if and was implemented in a way such that
    (and E1 E2) translates to (if E1 (if E2 #t #f) #f)
    or equivalently, if
    (and E) translates to (if E #t #f)
  3. In question 1a of Assignment #2 we specified a restricted set of expressions that evaluate to S-expression values. Is there any reason for not including things like car — so expressions like
    (cons (car (list 1)) (cdr (list 1)))
    are not included in the language? Well, yes, of course. Explain it.
  4. We’ve seen this in class:
    (rewrite (protect f) => (lambda (x) (f x))) (define (make-recursive f) ((lambda (x) (x x)) (lambda (x) (f (protect (x x))))))
    Why did we need to define protect as a “rewrite rule” rather than a plain function?


 

Question 8: Schlac

 18 pts 

As we have seen, in the Schlac language we can represent pretty much anything that we can in “normal” programming languages. The main issue is choosing an appropriate encoding that satisfies the required properties (e.g., cons returning a value that contains its two inputs, and null being a value that null? can distinguish from all other pairs).

We’ve seen two different encodings for natural numbers — but we didn’t see an encoding for integers yet. In this question, you’ll fill up this hole.

A pretty obvious and convenient representation for integers is using pairs: represent an integer as a pair of a boolean and a natural number, where (cons #t N) stands for N and (cons #f N) for -N. (Note that this means that we have two different representations for 0, but that should not be a problem as long as your code treats both as 0.)

Using this, we essentially get a new “type” of values, with its own set of operations. In other words, using natural-number operations on integers encoded this way is not going to work.

We define a tiny library of operations for these integers, using an i: prefix to distinguish integer operations:

;; i:zero? : Int -> Bool ;; determines if an integer is zero (define i:zero? (lambda (n) (zero? (cdr n)))) ;; i:neg : Int -> Int ;; returns the negation of its input (define i:neg (lambda (n) (cons (not (car n)) (cdr n))))
Defining subtraction is easy if we have addition, and i:= follows:
;; i:- : Int Int -> Int ;; subtracts two integers, which is easy using addition (define i:- (lambda (m n) (i:+ m (i:neg n)))) ;; i:= : Int Int -> Bool ;; compares two integer numbers (define i:= (lambda (n m) (i:zero? (i:- m n))))

So the only problem is now defining an integer addition operation, i:+. Consider that the result of (sub1 n) is 0 when n is 0, and therefore (- n m) is 0 if m is bigger than or equal to n. Using this idea, we can implement a utility -- function that subtracts two natural numbers and returns the resulting integer result.

;; Utility rewrite rule for easy bindings (rewrite (with id E1 E2) => ((lambda (id) E2) E1)) ;; -- : Nat Nat -> Int ;; This utility function subtracts two natural numbers and ;; returns an integer number result. (define -- (lambda (m n) (with x (- m n) (with y (- n m) (if (zero? x) (cons #f y) (cons #t x))))))

The last piece that is missing now is i:+, and this should be relatively easy. Complete its definition. (Note: you can assume that everything that we defined in class is given.)

;; i:+ : Int Int -> Int ;; adds two integer numbers (define i:+ (lambda (m n) —«fill-in»—))