Sample Midterm 1Date: Tuesday, February 16th | |
| |
AdministrativeThis is a sample midterm that was given in the 2008 spring 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 | 8 pts |
Define a find-prev function that consumes a value and a list, and returns
the value that appears before this value in the list. The function throws a
“not found” error if the value is not found in the list, or if it is the
first item in the list. If the value is found in several places in the list,
your implementation is allowed to return either one. The following tests
demonstrate how this function should work. Note how the second test checks
for any valid result.
(test (find-prev 1 (list 0 1 2 3)) => 0)
(test (member (find-prev 'x (list 'a 'x 'b 'x 'c 'd 'x))
(list 'a 'b 'd)))
(test (find-prev 5 (list 0 1 2 3)) =error> "not found")
(test (find-prev 5 null) =error> "not found") |
Write the definition with a correct type declaration, and a purpose
statement.
Hint: You can use match to make your code simpler. Alternatively, you
can use member and reverse to implement the function (reminder:
(member x l) returns either the tail of l beginning with x, or
#f if it is not found); a natural solution using this will use the last
found item.
Question 2: Higher-Order Functions | 12 pts |
In this question we will deal with repeated applications of a function f
over an initial input value. To do this, define a function called series
that consumes three arguments: a function f, an initial value init,
and a length n. The result of the function will be a list holding n
elements: init, (f init), ...,
(f (f ... (f init) ...)), where the last one is basically
(fn-1 init). Again, here are a few tests to
demonstrate how the function should work:
(test (series add1 10 0) => '())
(test (series add1 10 5) => (list 10 11 12 13 14))
(test (series rest (list 1 2 3) 3)
=> (list '(1 2 3) '(2 3) '(3)))
(test (series (lambda (l) (map add1 l))
(list 1 2 3)
3)
=> (list '(1 2 3) '(2 3 4) '(3 4 5))) |
Question 3: BNF Grammar | 12 pts |
The following is the Schlac grammar, as given in class:
<SCHLAC> ::= <id> [1]
| (<SCHLAC> <SCHLAC> <SCHLAC> ...) [2]
| (lambda (<id> <id> ...) <SCHLAC>) [3]
| (define <id> <SCHLAC>) [4] |
Which if the following expressions are valid Schlac expressions according to
this grammar?
- (lambda (x) (+ 1 2))
- (lambda (x) (lambda (y) x))
- (lambda (x) (lambda () x))
- (lambda (x) (x))
- (lambda (x) (x x x))
- (lambda ((x)) x)
- (lambda (x) (x x (x x)))
- (lambda (x) (x x (lambda x x)))
- ((lambda (x) (x x)) (lambda (x) (x x)))
- (define omega = ((lambda (x) (x x)) (lambda (x) (x x))))
- (define + 3)
- (lambda (1) (+ 1 2))
Choose one of the valid expressions, and show how to derive it formally.
Remember that your derivation should have the list of rule names that are
used to produce it.
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.)
Do this for these expressions:
- (* blank 7) ; no need for a calculator
- (* blank blank)
- ((blank (blank)))
- (blank ((lambda (x) (x x)) (lambda (x) (x x))))
- ((lambda (blank) (blank blank)) blank)
Question 5: Structural Recursion with ASTs | 18 pts |
(Use the Flang source at the end of the exam for this
question.)
A useful utility for code that manipulates programs is a free-vars
function: given an expression in AST form, it returns a list of names that
appear free in it. This is a useful function because it can be used to
implement other useful tools like asking whether an identifier is free in
an expression, determining whether an expression is closed (has no free
identifiers) or not, etc; and these tools are useful in various tasks
from detecting errors to program optimization.
Given the type definition for the Flang abstract syntax
tree, write this free-vars function. It should consume a FLANG
expression value and return a list of free identifiers that it contains.
The order of names in the resulting list and possible repetitions do not
matter — so tests for this function should use set equality:
(test (set=? '(x) (free-vars (parse "x"))))
(test (set=? '() (free-vars (parse "3"))))
(test (set=? '(x) (free-vars (parse "{+ x x}"))))
(test (set=? '(y) (free-vars (parse "{with {x 1} {+ x y}}"))))
(test (set=? '(x) (free-vars (parse "{with {x x} {+ x 1}}")))) |
Question 6: Language Extension | 18 pts |
(Use the Flang source here too.)
As we have seen, the semantics of call, like the semantics of
with, are eager: the argument is evaluated before the
substitution happens. We want to extend the language with a lazy-call
expression that is similar to call except that it is lazy. To do
this, we begin by extending the BNF, the FLANG type definition, and
adapting the parser. The new BNF entry and variant are:
; <FLANG> ::= ...
; | { call <FLANG> <FLANG> }
; | { lazy-call <FLANG> <FLANG> }
(define-type FLANG
...
[Call (fun-expr FLANG) (arg-expr FLANG)]
[LCall (fun-expr FLANG) (arg-expr FLANG)]) |
You now need to implement the rest of the changes. These are:
- The formal substitution rule for lazy-call,
- A case for LCall in subst,
- The formal evaluation rule for lazy-call,
- A case for LCall in eval.
Question 7: Language Features | 25 pts |
- Write a proper test case for the new lazy-call expression from the
previous question. The test case should demonstrate that the new call is
indeed lazy.
- Are the lazy-call evaluation rule and code fragment in the
previous question completely lazy? Explain (briefly).
- If your evaluation of lazy-call is correct, it uses subst with
an expression rather than a value. Why is it still fine to evaluate
{with {x 1} {lazy-call {fun {y} {with {x 2} y}} x}} |
when it looks like it might lead to name capture?
- In Assignment #3 we implemented the boolean constants True and False
in the parser:
(define (parse-sexpr sexpr)
(match sexpr
...
;; the constants must precede the Id case
['True (Bool #t)]
['False (Bool #f)]
...)) |
In an environment-based evaluator, we could simplify the parser: parse
them as identifiers, then start the evaluation with an initial environment
that has bindings for the two identifiers. This will, however, change the
semantics of the language. Write an expression that demonstrates the
change. (Hint: you can see the same change in Scheme, when you use #t
and #f, vs using true and false.)
- When we wrote the typed definition of the fixed-point combinator, we
had a Tau type defined for x:
(: make-recursive : (All (S T) (((S -> T) -> (S -> T)) -> (S -> T))))
(define (make-recursive f)
(define-type (Tau S T) = (Rec this (this -> (S -> T))))
((lambda: ([x : (Tau S T)]) (f (lambda: ([z : S]) ((x x) z))))
(lambda: ([x : (Tau S T)]) (f (lambda: ([z : S]) ((x x) z)))))) |
Is this definition really needed? Explain why (briefly!) or show how it
can be written without the definition.
Question 8: Schlac | 16 pts |
We know that there are many ways in which we can encode numbers and other
basic types in the lambda calculus. Here is one such encoding for numbers:
(define 0 (lambda (f x) f))
(define 1 (lambda (f x) (f x)))
(define 2 (lambda (f x) ((f x) x)))
(define 3 (lambda (f x) (((f x) x) x)))
... |
Define add1 for this encoding.
For a bonus, define + too. (But this can be more difficult, so feel free
to skip it.)
The Flang Implementation
The following is the Flang implementation that uses substitution. Use it as
a reference for related questions.
This is for reference only, 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:
subst:
N[v/x] = N
{+ E1 E2}[v/x] = {+ E1[v/x] E2[v/x]}
{- E1 E2}[v/x] = {- E1[v/x] E2[v/x]}
{* E1 E2}[v/x] = {* E1[v/x] E2[v/x]}
{/ E1 E2}[v/x] = {/ E1[v/x] E2[v/x]}
y[v/x] = y
x[v/x] = v
{with {y E1} E2}[v/x] = {with {y E1[v/x]} E2[v/x]} ; if y =/= x
{with {x E1} E2}[v/x] = {with {x E1[v/x]} E2}
{call E1 E2}[v/x] = {call E1[v/x] E2[v/x]}
{fun {y} E}[v/x] = {fun {y} E[v/x]} ; if y =/= x
{fun {x} E}[v/x] = {fun {x} E}
eval:
eval(N) = N
eval({+ E1 E2}) = eval(E1) + eval(E2) \ if both E1 and E2
eval({- E1 E2}) = eval(E1) - eval(E2) \ evaluate to numbers
eval({* E1 E2}) = eval(E1) * eval(E2) / otherwise error!
eval({/ E1 E2}) = eval(E1) / eval(E2) /
eval(id) = error!
eval({with {x E1} E2}) = eval(E2[eval(E1)/x])
eval(FUN) = FUN ; assuming FUN is a function expression
eval({call E1 E2}) = eval(Ef[eval(E2)/x]) if eval(E1)={fun {x} Ef}
= 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)
[...])
(: parse : String -> FLANG)
;; parses a string containing a FLANG expression to a FLANG AST
(define (parse str)
[...])
(: subst : FLANG Symbol FLANG -> FLANG)
;; substitutes the second argument with the third argument in the
;; first argument, as per the rules of substitution; the resulting
;; expression contains no free instances of the second argument
(define (subst expr from to)
(cases expr
[(Num n) expr]
[(Add l r) (Add (subst l from to) (subst r from to))]
[(Sub l r) (Sub (subst l from to) (subst r from to))]
[(Mul l r) (Mul (subst l from to) (subst r from to))]
[(Div l r) (Div (subst l from to) (subst r from to))]
[(Id name) (if (eq? name from) to expr)]
[(With bound-id named-expr bound-body)
(With bound-id
(subst named-expr from to)
(if (eq? bound-id from)
bound-body
(subst bound-body from to)))]
[(Call l r) (Call (subst l from to) (subst r from to))]
[(Fun bound-id bound-body)
(if (eq? bound-id from)
expr
(Fun bound-id (subst bound-body from to)))]))
(: arith-op : (Number Number -> Number) FLANG FLANG -> FLANG)
;; gets a Scheme numeric binary operator, and uses it within a FLANG
;; ‘Num’ wrapper
(define (arith-op op expr1 expr2)
(: Num->number : FLANG -> Number)
(define (Num->number e)
(cases e
[(Num n) n]
[else (error ’arith-op "expects a number, got: ~s" e)]))
(Num (op (Num->number expr1) (Num->number expr2))))
(: eval : FLANG -> FLANG)
;; evaluates FLANG expressions by reducing them to *expressions*
(define (eval expr)
(cases expr
[(Num n) expr]
[(Add l r) (arith-op + (eval l) (eval r))]
[(Sub l r) (arith-op - (eval l) (eval r))]
[(Mul l r) (arith-op * (eval l) (eval r))]
[(Div l r) (arith-op / (eval l) (eval r))]
[(With bound-id named-expr bound-body)
(eval (subst bound-body
bound-id
(eval named-expr)))]
[(Id name) (error ’eval "free identifier: ~s" name)]
[(Fun bound-id bound-body) expr]
[(Call fun-expr arg-expr)
(let ([fval (eval fun-expr)])
(cases fval
[(Fun bound-id bound-body)
(eval (subst bound-body
bound-id
(eval arg-expr)))]
[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))])
(cases result
[(Num n) n]
[else (error ’run
"evaluation returned a non-number: ~s" result)])))