Sample Midterm 2

Date: Monday, October 27th

 
Name:

Administrative

This is a sample midterm that was given last 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) Simple Scheme Programming /8
(2) Higher-Order Functions /12
(3) BNF Grammar /12
(4) Fill in the blanks /15
(5) Structural Recursion with ASTs /18
(6) Language Extension /18
(7) Language Features /25
(8) Schlac /16
Total /124
 


 

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?
  1. (lambda (x) (+ 1 2))
  2. (lambda (x) (lambda (y) x))
  3. (lambda (x) (lambda () x))
  4. (lambda (x) (x))
  5. (lambda (x) (x x x))
  6. (lambda ((x)) x)
  7. (lambda (x) (x x (x x)))
  8. (lambda (x) (x x (lambda x x)))
  9. ((lambda (x) (x x)) (lambda (x) (x x)))
  10. (define omega = ((lambda (x) (x x)) (lambda (x) (x x))))
  11. (define + 3)
  12. (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:
(* (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.)

Do this for these expressions:
  1. (* blank 7) ; no need for a calculator
  2. (* blank blank)
  3. ((blank (blank)))
  4. (blank ((lambda (x) (x x)) (lambda (x) (x x))))
  5. ((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:
  1. The formal substitution rule for lazy-call,
  2. A case for LCall in subst,
  3. The formal evaluation rule for lazy-call,
  4. A case for LCall in eval.



 

Question 7: Language Features

 25 pts 

  1. 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.
  2. Are the lazy-call evaluation rule and code fragment in the previous question completely lazy? Explain (briefly).
  3. 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?
  4. 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.)
  5. 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 CSU660

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