Sample Midterm 2Date: Tuesday, February 16th | |
| |
AdministrativeThis is a sample midterm that was given in the 2008 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”.
(At the end of the exam there is a printout of the Flang
implementation, please tear it off now. Do not hand in the exam with it.) | |
| |
Question 1: Simple Scheme Programming | 9 pts |
Define an all-equal? function that consumes a list of values, and returns
#t if the values are all equal?. Here are a few test cases that
highlight cases that you need to take care of:
(test (all-equal? (list 1 2 3)) => #f)
(test (all-equal? (list 1 1 1)) => #t)
(test (all-equal? (list (list 1 2) (list 1 2))) => #t)
(test (all-equal? (list "foo" "foo")) => #t)
(test (all-equal? (list 'blah)) => #t)
(test (all-equal? null) => #t) |
Write the definition with a correct type declaration, and a purpose
statement.
Hint: You can (and should) use andmap or ormap to make your code very
simple.
Question 2: Higher-Order Functions | 12 pts |
As you know, the andmap function is expecting a predicate and a list of
values, and returns #t if the predicate is true on all of the list items.
For the purpose of this question, you can assume that it returns a boolean
result:
(: andmap : (A -> Boolean) (Listof A) -> Boolean) |
Write a function called andpreds which is a twist on andmap: it
expects a list of predicate functions and a single value and returns #t
if all of the functions are true for the value. Again, here are a few tests
to demonstrate how the function should work:
(test (andpreds (list even?) 10) => #t)
(test (andpreds (list integer? even?) 10) => #t)
(test (andpreds (list string? even?) 10) => #f)
(test (andpreds (list string?) null) => #f)
(test (andpreds (list list?) (list 1 2)) => #t)
(test (andpreds (list list? null?) null) => #t)
(test (andpreds null 10) => #t) |
Hint: using andmap will make it very easy; you can assume the above
boolean value (that is, ignore the fact that it actually returns the last
result if all are not #f).
Remember to write the type and purpose statement.
Question 3: BNF Grammar | 15 pts |
Several software packages accept a specification for “pages to print” as a
comma separated sequence of integers and integer ranges (two integers
separated by a dash). For example,
Write a BNF for such specification. Notes:
- You can ignore white spaces (as usual)
- You can use ‘...’ as usual, but it won’t make it shorter
- You cannot assume that something like <int> is known — you must
specify the grammar completely
- Integers are actually plain natural numbers (decimal, no sign)
- You do, however, need to make sure that numbers do not begin with
redundant leading zeros, for example 120 and 0 are valid, but
0120 and 00 are not.
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:
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.)
You do not need to write types for definitions, except when the
question asks for it.
Do this for these expressions:
- (blank (random 1000)) ; random does the usual
- (blank + 60)
- (blank blank) ; easy, but write a type too
- (or ’blank (blank))
- (let ([blank 330]) blank)
Question 5: Language Extension | 18 pts |
(Use the Flang source at the end of the exam for
this question.)
We wish to extend the Flang language with two-argument (binary) functions.
We begin by extending the BNF with two new rules for binary functions and
binary function applications:
<FLANG> ::= ... same as before ...
| { fun2 { <id> <id> } <FLANG> }
| { call2 <FLANG> <FLANG> <FLANG> }
(define-type FLANG
... same as before ...
[Fun2 (name1 Symbol) (name2 Symbol) (body FLANG)]
[Call2 (fun-expr FLANG) (arg1-expr FLANG) (arg2-expr FLANG)]) |
You can assume that the parser was modified appropriately. Specifically,
you should assume that the parser will throw an error if the two
argument names in a fun2 expression are not different. In addition,
the VAL type is extended with a new kind of closure type that holds two
argument names:
(define-type VAL
... same as before ...
[FunV2 (name1 Symbol) (name2 Symbol) (body FLANG) (env ENV)]) |
Begin by extending the formal evaluation rules with cases for the two new
kinds of expressions fun2 and call2. You need to write only these
two rules, since the rest of the rules are not modified. Complete the
question with the extended eval function — again, you need only the
two new cases branches for Fun2 and Call2 because the rest of
the code is the same.
Question 6: Structural Recursion with ASTs | 20 pts |
A different approach for implementing the new fun2 and call2
expressions is one that we used previously: instead of extending the
evaluator, we can translate the new expressions to other expression that
eval already knows how to deal with. Specifically, we can convert
fun2 expressions to curried fun expressions, and call2 to curried
call application expressions. This can be achieved using the following
two rewrite rules:
{fun2 {x y} E} -> {fun {x} {fun {y} E}}
{call2 f x y} -> {call {call f x} y} |
which you will implement in the form of a new preprocess function.
This will be “injected” to the evaluator by changing run to be:
(: run : String -> Number)
;; evaluate a FLANG program contained in a string
(define (run str)
(let ([result (eval (preprocess (parse str)) (EmptyEnv))])
... same as before ...)) |
First of all, the two formal evaluation rules should reflect this change,
write the new rules. When you’re done, implement the preprocess function
that implements these rewrites.
Question 7: Language Features | 25 pts |
Important: this question is hard, don’t get stuck on it before you finish
the rest of the exam. Note that all answers should be quite
short.
- Consider the above two implementations of fun2 and call2. The
two implementations result in a different semantics for these expressions.
Find an expression that can reveal the difference, by evaluating
differently based on which implementation is used.
- The difference between the two binary function extensions is pointing
at a trade-off: each implementation can claim a feature that the other
lacks. Find such a feature for each side.
- In the first implementation of the binary functions it was important
to assume that the two argument names are different. Explain why this
assumption is important in that implementation, and what you can gain by
removing the assumption from the second one.
- In a language implementation like the attached
Flang code, do we really need a garbage collector?
Explain.
- What if we were to change this implementation to use dynamic scope?
— Would we need a garbage collector then? (Note: this is a hard
question!)
Question 8: The Y Combinator | 10 pts |
Our version of the Y combinator is limited to one-argument functions. Is
this a problem? Explain if so, or describe how it can be used for multiple
arguments if not. Either way, your answer should be very short. (A correct
answer can be one word!)
The Flang Implementation
The following is the Flang implementation that uses environments. Use it as
a reference for related questions.
Please tear this part off, do not hand it in it with the rest of the exam.
Do not write solutions on these pages.
#lang pl
#|
The grammar:
<FLANG> ::= <num>
| { + <FLANG> <FLANG> }
| { - <FLANG> <FLANG> }
| { * <FLANG> <FLANG> }
| { / <FLANG> <FLANG> }
| { with { <id> <FLANG> } <FLANG> }
| <id>
| { fun { <id> } <FLANG> }
| { call <FLANG> <FLANG> }
Evaluation rules:
eval(N,env) = N
eval({+ E1 E2},env) = eval(E1,env) + eval(E2,env)
eval({- E1 E2},env) = eval(E1,env) - eval(E2,env)
eval({* E1 E2},env) = eval(E1,env) * eval(E2,env)
eval({/ E1 E2},env) = eval(E1,env) / eval(E2,env)
eval(x,env) = lookup(x,env)
eval({with {x E1} E2},env) = eval(E2,extend(x,eval(E1,env),env))
eval({fun {x} E},env) = <{fun {x} E},env>
eval({call E1 E2},env1)
= eval(Ef,extend(x,eval(E2,env1),env2))
if eval(E1,env1)=<{fun {x} Ef},env2>
= error! otherwise
|#
(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)]
[Call (fun-expr FLANG) (arg-expr FLANG)])
(: parse-sexpr : Sexpr -> FLANG)
;; to convert s-expressions into FLANGs
(define (parse-sexpr sexpr)
(match sexpr
[(number: n) (Num n)]
[(symbol: name) (Id name)]
[(cons ’with more)
(match sexpr
[(list ’with (list (symbol: name) named) body)
(With name (parse-sexpr named) (parse-sexpr body))]
[else (error ’parse-sexpr "bad ‘with’ syntax in ~s" sexpr)])]
[(cons ’fun more)
(match sexpr
[(list ’fun (list (symbol: name)) body)
(Fun name (parse-sexpr body))]
[else (error ’parse-sexpr "bad ‘fun’ syntax in ~s" sexpr)])]
[(list ’+ lhs rhs) (Add (parse-sexpr lhs) (parse-sexpr rhs))]
[(list ’- lhs rhs) (Sub (parse-sexpr lhs) (parse-sexpr rhs))]
[(list ’* lhs rhs) (Mul (parse-sexpr lhs) (parse-sexpr rhs))]
[(list ’/ lhs rhs) (Div (parse-sexpr lhs) (parse-sexpr rhs))]
[(list ’call fun arg) (Call (parse-sexpr fun) (parse-sexpr arg))]
[else (error ’parse-sexpr "bad syntax in ~s" sexpr)]))
(: parse : String -> FLANG)
;; parses a string containing a FLANG expression to a FLANG AST
(define (parse str)
(parse-sexpr (string->sexpr str)))
;; Types for environments, values, and a lookup function
(define-type ENV
[EmptyEnv]
[Extend (id Symbol) (v VAL) (rest-env ENV)])
(define-type VAL
[NumV (n Number)]
[FunV (name Symbol) (body FLANG) (env ENV)])
(: lookup : Symbol ENV -> VAL)
(define (lookup name env)
(cases env
[(EmptyEnv) (error ’lookup "no binding for ~s" name)]
[(Extend id val rest-env)
(if (eq? id name) val (lookup name rest-env))]))
(: arith-op : (Number Number -> Number) VAL VAL -> VAL)
;; gets a Scheme numeric binary operator, and uses it within a NumV
;; wrapper
(define (arith-op op val1 val2)
(: NumV->number : VAL -> Number)
(define (NumV->number v)
(cases v
[(NumV n) n]
[else (error ’arith-op "expects a number, got: ~s" v)]))
(NumV (op (NumV->number val1) (NumV->number val2))))
(: eval : FLANG ENV -> VAL)
;; evaluates FLANG expressions by reducing them to values
(define (eval expr env)
(cases expr
[(Num n) (NumV n)]
[(Add l r) (arith-op + (eval l env) (eval r env))]
[(Sub l r) (arith-op - (eval l env) (eval r env))]
[(Mul l r) (arith-op * (eval l env) (eval r env))]
[(Div l r) (arith-op / (eval l env) (eval r env))]
[(With bound-id named-expr bound-body)
(eval bound-body
(Extend bound-id (eval named-expr env) env))]
[(Id name) (lookup name env)]
[(Fun bound-id bound-body)
(FunV bound-id bound-body env)]
[(Call fun-expr arg-expr)
(let ([fval (eval fun-expr env)])
(cases fval
[(FunV bound-id bound-body f-env)
(eval bound-body
(Extend bound-id (eval arg-expr env) f-env))]
[else (error ’eval "‘call’ expects a function, got: ~s"
fval)]))]))
(: run : String -> Number)
;; evaluate a FLANG program contained in a string
(define (run str)
(let ([result (eval (parse str) (EmptyEnv))])
(cases result
[(NumV n) n]
[else (error ’run
"evaluation returned a non-number: ~s" result)])))