MidtermDate: Friday, March 14th | |
| |
AdministrativeThis exam is intended for about two hours, but you have no time limit (up
to a reasonable hour). The question scores sum up to a total of
124 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.
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 identifers, 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 typed-scheme
#|
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 an 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 an FLANG
;; `Num' wrapper
(define (arith-op op expr1 expr2)
(define: (Num->number [e : FLANG]) : Number
(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)])))