Sample Midterm 4Date: Tuesday, February 16th |
AdministrativeThis is a sample midterm that was given in the 2009 fall semester.
|
| |||||||||||||||||||||||||||||||
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) => '()) |
Question 2: Higher-Order Functions | 18 pts |
(define snoc (flip cons))
(test (snoc '(2 3) 1) => '(1 2 3))
(test ((flip -) 1 2) => 1) |
(define foo ((currify map) ((currify (flip -)) 5))) |
(: 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> } |
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 |
(* (blank) 2) |
(define blank (lambda () 330)) |
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}}}} |
{with {x 1}
{with {y 2}
{fun {x}
{with {y 3}
{+ x y}}}}} |
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)]) |
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:
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}}} |
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 |
(: 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))])) |
(and E1 E2) translates to (if E1 (if E2 #t #f) #f) |
(and E) translates to (if E #t #f) |
(cons (car (list 1)) (cdr (list 1))) |
(rewrite (protect f) => (lambda (x) (f x)))
(define (make-recursive f)
((lambda (x) (x x)) (lambda (x) (f (protect (x x)))))) |
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)))) |
;; 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»—)) |