Sample Midterm 3

Date: Tuesday, February 16th

 
Name:

Administrative

This is a sample midterm that was given in the 2009 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 Grade  
(1) Scheme Programming /20
(2) BNF Grammar /18
(3) Fill in the blanks /20
(4) Structural Recursion with ASTs /25
(5) Language Extension /18
(6) Language Features /15
(7) Schlac /15
Total /131
 


 

Question 1: Scheme Programming

 20 pts 

We can use a mapping function to specify an arbitrary graph — the function will consume a node name and return a list of node names that are reachable from it: A -> (Listof A). Note that this is using A, to keep the actual type of node names abstract (for example, one graph might use numbers as labels, and another can use symbols). Using this representation, you need to write a find-path function that looks for a path between two given nodes:

(: find-path : —«fill-in»—) (define (find-path from to graph) —«fill-in»—)

Write the function, implementing a simple recursive depth-first search (that is, no need for fancy improvements like breadth-first search) that looks for a path. If such a path exists, the result will be a list whose first element is from and whose last element is last. If no such path exists, then the result should be #f.

To test this function, it is best if we have a function that consumes a list of node specifications, each a list of a node name and the nodes you can reach from it, and returns a mapping function:

(: spec->graph : (All (A) ((Listof (Pair A (Listof A))) -> (A -> (Listof A))))) ;; converst a graph specification as a list of lists to a mapping ;; function (define (spec->graph spec) (lambda (node-name) ;; note: assoc returns a sublist whose first value is equal to ;; `node-name' (let ([node-spec (assoc node-name spec)]) (if node-spec (rest node-spec) (error 'graph "no such node: ~s" node-name))))) ;; now we can use it for a test (test (find-path 'a 'f (spec->graph '((a b) (b c) (c a d e) (d) (e e d f) (f d)))) => (list 'a 'b 'c 'e 'f))
Note also that the graph might have cycles in it (note in the above example the cycle of a b c and note that e cycles to itself), and it can have nodes that have no outgoing edges (for example, d in the example — so applying the resuling mapping function on 'd will return an empty list). However, you can rely on the fact that the function represents a finite graph — that is, it cannot get you stuck going forever. (For example,(lambda (n) (list (add1 n))) would represent an infinite graph — you don’t need to worry about such things.)

To avoid getting stuck in a loop, you need to make sure that you don’t go to a node that you’ve already visited. Luckily, you need to collect the nodes that you visit anyway (to return as a result), so it is natural to write a solution that will use an extra argument to collect the path, and use it to avoid loops (hint: use member). Using this, the find-path function itself would be a one-liner (it will also need to use reverse). Make your helper function look like:

(: find-helper : —«fill-in»—) (define (find-helper from to graph visited) —«fill-in»—)
where visited will be a list of nodes that you’ve visited in the current call. (They will be accumulated in reverse order.)

Reminder: make sure that you write a correct type for both functions, and a clear purpose statement for find-path.



 


 

Question 2: BNF Grammar

 18 pts 

The following grammar is a failed attempt at getting an infix arithmetic language:
<EXPR> ::= <EXPR> <OP> <EXPR> | ( <EXPR> ) | <num> <op> ::= + | - | * | /
The problem is that this grammar is ambiguous. Demonstrate this.
The way to do this should be in two sequences that show how a single expression can be derived in two different ways. You don’t need to specify the rules that you use at each step, just the derivation sequence that begins with <EXPR>.


 

Question 3: Fill in the blanks

 20 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:
(* (blank) 2)
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:
  1. (+ blank (length blank))
  2. ((blank "660"))
  3. ((blank blank) (blank blank))
  4. (or (not blank) blank)
  5. (let ([blink blank]) (blank blink))


 

Question 4: Structural Recursion with ASTs

 25 pts 

(Use the Flang source at the end of the exam for this question.)

Write an unparse function for the FLANG abstract syntax tree. You basically need to write a simple recursive function that will convert a given input expression into a string form that would parse to an equal expression AST. A convenient tool to create the strings is the format function. Here are some examples of using it to render a symbol and a number to a string, and an example that glues together several pieces:

(test (format "~s" 123) => "123") (test (format "~s" 'xy) => 'xy) (test (format "{foo {~s} ~a}" 'x "blah") => "{foo {x} blah}")
(These examples are only to clarify how format works.) Note how ~s is used in a place where a Scheme value should be rendered (only numbers and symbols are needed for this question), and ~a is used to have a string included in the result.

When you write your unparser, you will see that the cases for the arithmetic operators and for call are all very similar. This means that you should define a small internal helper function that will help constructing the resulting string.

Here is a utility function for testing, and a few tests:

(: verify-unparse : String -> Boolean) (define (verify-unparse expr) (let ([expr (parse expr)]) (equal? expr (parse (unparse expr))))) (test (verify-unparse "{with {id {fun {x} x}} {+ {call id 4} 6}}"))
A correct implementation means that any input that will be passed into verify-unparse as above should return #t (provided that it can be parsed as a valid FLANG expression).



 

Question 5: Language Extension

 18 pts 

(Use the Flang source at the end of the exam for this question.)

For some obscure reason, it turns out that performing multiplications is horrendously slow, and clients who use your FLANG implementations are complaining. In an attempt to make things run faster, we can modify the way that the interpreter evaluates Mul expressions: we can do the following:

To implement this change, we change the Mul case in the evaluator to:
[(Mul l r) (smart-mul (eval l env) (eval r env))]

Your job is to implement this smart-mul function so it will do the above. Important: make sure you use cases to inspect the two values.



 

Question 6: Language Features

 15 pts 

It turns out that your clients are so much happier with the optimized FLANG interpreter that you implemented in the previous question, that your friend decides to write such a function in for himself, to use in his Scheme code. He writes this:

(: fast-mul : Number Number -> Number) (define (fast-mul a b) (cond [(or (= a 0) (= b 0)) 0] [(= a 1) b] [(= b 1) a] [else (* a b)]))

When you see this code in the weekly code reviews, you notice that this function is not the same as the multiplication function. Explain why. Your explanation should be simple: an expression that demonstrates a case where (fast-mul m n) is not going to return the same result as (* m n).









Also, explain how using Typed Scheme avoided an even more dangerous change in semantics of this multiplication function. For this, you need to show a worse kind of a mistake that could result if we hadn’t used typed scheme.
Note: this part of the question can be very difficult, don’t get stuck on it if you don’t know the answer.



 

Question 7: Schlac

 15 pts 

In class, we have seen how the Lambda Calculus (or Schlac, in our case) can be used to encode values of various types as functions. One thing that we’ve seen is that certain functions can be viewed as an encoding of values of different types. For example,

  1. 1 (a number) is also the identity function (an A -> A function),
  2. #f (a boolean) is also 0 (a number),
  3. 2 (a number) is also a square operation (a Number -> Number function), and in fact, any encoded number n is a xn operation when used as a Number -> Number function.

There are many more functions that can be viewed as doing different things for different types. For example, consider or, defined as:

(define or (lambda (a b) (a a b)))
and view it as a Number -> Number function. When used as such a function, we can show that (or n) computes nn as follows:
(or n) expanding the `or' definition --> ((lambda (a b) (a a b)) n) expanding shorthands --> ((lambda (a) (lambda (b) ((a a) b))) n) reducing the immediate application --> (lambda (b) ((n n) b)) since everything is a function, this is the same as just --> (n n) and according to what we know about how numbers are viewed as unary numeric functions: --> nn

Show what or does when viewed as a binary numeric function, Number Number -> Number. Your explanation should be formal, similar to the above.



 

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.

(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)])))