MidtermDate: Thursday, February 25th, 3:00pm |
AdministrativeThis exam is intended for two hours. The question scores sum up to a total of 125 points, but it will be graded “out of a 100” so you can skip more than a whole question, small pieces, or just shoot for a bonus. |
| |||||||||||||||||||||||||||||||
Question 1: Simple Scheme Programming | 10 pts |
Define a precedes function that consumes two values and a list, and determines whether the first value appears before the second value in the list. The result should be true if and only if this is the case, and false otherwise. Here are a few test cases to demonstrate how this function works:
(test (precedes 1 2 '(1 2)))
(test (not (precedes 1 2 '(2 1))))
(test (precedes 2 1 '(2 1)))
(test (not (precedes 1 2 '())))
(test (not (precedes 1 2 '(3 4 5 6))))
(test (precedes 'b 'e '(a b c d e f))) |
Hints:
Question 2: Higher-Order Functions | 14 pts |
Write a commutative function that accepts a binary function and returns one that is guaranteed to be commutative. Obviously, it is undecidable whether a given function is commutative or not for all possible inputs — but we can take the approach of providing a guarantee for all actual uses of the function by calling the input function twice and throwing an error if the two results are not equal. Here are two tests that demonstrate how this is used:
(test ((commutative +) 1 2) => 3)
(test ((commutative -) 1 2) =error> "non-commutative function!") |
(test ((commutative string-append) "x" "x") => "xx")
(test ((commutative string-append) "x" "y")
=error> "non-commutative function!") |
Note: the resulting function will have to be twice slower than the input function, but don’t make it even slower than that!
Question 3: BNF Grammar | 12 pts |
There’s a known puzzle about a man trying to cross a river with a sheep, a wolf, and a head of lettuce — using a boat that can only hold him and one more item. As the puzzle goes, he must not leave the wolf alone with the sheep or the sheep alone with the lettuce. In this question you will write a grammar that can be used to describe an attempt to solve this puzzle. Here is how a valid solution looks like in this syntax:
begin
move(sheep, left, right);
move(empty, right, left);
move(lettuce, left, right);
move(sheep, right, left);
move(wolf, left, right);
move(empty, right, left);
move(sheep, left, right);
end |
There are different ways in which this grammar can be written. One extreme would be a grammar that has exactly the above, forcing all well-formed programs to be exactly this solution — but that doesn’t leave any place for mistakes. A saner option would be something like:
<PUZZLE> ::= ...fill-in...
<MOVE> ::= move(<OBJECT>, <SIDE>, <SIDE>); |
move(sheep, left, left); |
Write a BNF that is a little more strict: a possible solution is one that is enclosed in begin...end tokens, with an odd number of moves in between, where the first and the last moves go from the left side to the right, and the moves properly alternate directions.
(Note that this still leaves some nonsensical options, like taking the sheep three times from the left to the right. It is possible to make a syntax that accounts for the contents of each side too, and it is even possible to make the grammar specify all correct solutions. However, these alternatives will be more verbose.)
Question 4: Fill in the blanks | 15 pts |
(* (blank) 2) |
(define blank (lambda () 330)) |
Question 5: Structural Recursion with ASTs | 20 pts |
Write a normalize function that takes in a FLANG and a list of symbols and returns a new FLANG that uses these identifiers in sequence from left to right for all of the bound input names. Use the substitution-based FLANG evaluator for this. For simplicity, you can assume that the list of symbols has enough elements, and that the input expression is closed (has no free identifiers).
For example (using parse for the expected output):
(test (normalize (parse "{with {x1 1} {with {x2 2} {+ x2 x1}}}")
'(foo bar baz))
=> (parse "{with {foo 1} {with {bar 2} {+ bar foo}}}")) |
(: norm : String -> FLANG)
(define (norm str)
(normalize (parse str) '(a b c d e f)))
(test (norm "{+ 1 2}")
=> (parse "{+ 1 2}"))
(test (norm "{with {x 1} {+ 1 2}}")
=> (parse "{with {a 1} {+ 1 2}}"))
(test (norm "{with {x 1} {+ {fun {x} x} {with {x 2} x}}}")
=> (parse "{with {a 1} {+ {fun {b} b} {with {c 2} c}}}"))
(test (norm "{with {x {with {x 1} x}} {+ x {fun {x} x}}}")
=> (parse "{with {a {with {b 1} b}} {+ a {fun {c} c}}}")) |
Note that you’ll need to define a helper function that returns two values: the normalized FLANG expression, and the list of leftover symbols. Use this declaration:
(: normalize* : FLANG (Listof Symbol)
-> (List FLANG (Listof Symbol))) |
Hint: since you’re using FLANG, you have subst — using it will make the solution very easy.
Question 6: Extending the FLANG language | 18 pts |
A relatively recent dialect of Lisp is “newLISP®”; its most obvious feature is that it is a dynamically-scoped “interpreted” language — and this is considered a feature (though you won’t find it listed on the front page). Regardless of whether this is a good idea or not, we can play with a feature that is central to it: first-class environments.
The idea of the extension that we’re going to implement is that at any point you can grab the current environment (as a new kind of value), and later on you can use it for some computation. Note that we will be extending the environment-based FLANG-ENV code which is lexically scoped, but the extension itself will make the language have dynamic-scope properties.
Our extension will involve two new expressions:
<FLANG> ::= ...same...
{get-scope}
{with-scope E1 E2} |
Here’s an example that should demonstrate this:
(test (run "{with {e {with {x 1} {with {y 2} {get-scope}}}}
{with-scope e {+ x y}}}")
=> 3) |
(test (run "{with {add {fun {x} {with {r {fun {y} {+ x y}}}
{get-scope}}}}
{with-scope {call add 1} {call r 2}}}")
=> 3) |
To implement this, we begin with an extension of the AST:
(define-type FLANG
...same...
[GetScope]
[WithScope (scope-expr FLANG) (body FLANG)]) |
(define-type VAL
...same...
[ScopeV (env ENV)]) |
The last thing to do is to implement the evaluation fragments for the two new expressions. Do that.
Question 7: Language Features | 20 pts |
Question 8: Schlac | 16 pts |
In class we have seen two ways to define not for our lambda encoding of booleans:
(define not1 (lambda (a) (a #f #t)))
(define not2 (lambda (a) (lambda (x y) (a y x)))) |
We will show this now formally as follows: take both definition as a function from two-argument-numeric-functions (Number Number -> Number) to two-argument-numeric-functions functions and see that they produce different output functions for the same input. We only need one counter-example, so we’ll use + as our two-argument-numeric-function and show that (not1 +) and (not2 +) return two different two-argument-numeric-function.
First, begin with (not2 +) — to see what it means, we’ll apply it to two arbitrary encoded church numerals, and reduce things from there:
((not2 +) A B)
(((lambda (a) (lambda (x y) (a y x))) +) A B) ; definition of not2
((lambda (x y) (+ y x)) A B) ; application step
(+ B A) ; another application |
The second step is to do the same with (not1 +):
((not1 +) A B)
(((lambda (a) (a #f #t)) +) A B)
((+ #f #t) A B)
... |
[This question is optional, of course. Feel free to write more details, give a single word reply for each item, or completely ignore it if you’re not interested.]