Sample Final 4
Date: Thursday, April 18th

 
Name:

Administrative

This exam was given in the Fall semester of 2012.

Question Grade  
(1) Racket Programming /20
(2) Type Systems Part I /12
(3) Type Systems Part II /16
(4) Lazy Language /16
(5) Continuations and Macros Part I /16
(6) Macros and Continuations Part II /18
(7) Language Features /18
Total /116
 

Remember: the correct answers are short. Also, questions that ask for a verbal answer rather than code should still be answered correctly and based on facts.



 

Question 1: Racket Programming
 20 pts 


Implement a delayed function that receives an arbitrary unary function f, and a (non-empty) list of appropriate inputs xs, and produces a delayed version d. During the creation of d, f should be called on each of the given inputs, and the list of results should be remembered as a “buffer”. From now on, whenever the resulting d is called, it will call f on that value and save it at the end of the buffer but return and remove the first value in it. An example will clarify this better:

(define f1 (delayed add1 '(0)))
(test (f1 10) => 1)
(test (f1 20) => 11)
(test (f1 30) => 21)
(define f2 (delayed list '(x y z)))
(test (f2 'a) => '(x))
(test (f2 'b) => '(y))
(test (f2 'c) => '(z))
(test (f2 'd) => '(a))
(test (f1 40) => 31)

Note that you must use a proper type (f can have any kind of input and output). In addition, using append to implement a queue is usually not a good idea since it’s inefficient, but don’t worry about this in your implementation.



 

Question 2: Type Systems Part I
 12 pts 


We want to extend the Picky language with a new type: pairs. This requires adding three new forms — pair to create a pair, and fst and snd to access it:

<PICKY> ::= <num>
          | <id>
          | ...
          | {pair E1 E2}
          | {fst E}
          | {snd E}
(Note that these pairs can be arbitrary, don’t confuse them with our cons which was used to create lists.)

Extend the type judgments with three rules for these — use x for a pair, as in A x B. Note that pair is a syntax in code, and x is a syntax in types — just like fun and -> respectively.

(You can assume that you’re extending the rules in the PICKY3 source code, though for this question it doesn’t matter which version you use.)



 

Question 3: Type Systems Part II
 16 pts 


Now that we have the type judgments in place, we need to continue and implement the new functionality. We do this as an extension of the PICKY3 source code (and it does matter here).

The usual bits are easy:

Now that the evaluator is properly extended, we need to implement the interesting bits — making the type checker deal with pairs. There are three places that need to be extended.

  1. Extend the definition of the TYPE type with a new variant for pairs. Use CoupleT for the new variant. (Using PairT would be an obvious name for it, but it’s already used in the code (in the 2TYPES utility definition).)
  2. Extend same-type to properly deal with pairs. Hint: this function has one case for each type, not for each variant in the AST, so you need only one new case.
  3. Extend the main typechecker code — the typecheck function. This will need one case for each of the three expression.
For each of these, please write only the new case(s) that need to be added, don’t copy and edit the whole code.



 

Question 4: Lazy Language
 16 pts 


As we’ve seen, there are similarities between the CPS conversion that we used for the implementation of the web language and our encoding of side-effects in a lazy language. One way to make this similarity concrete would be to implement similar functionality to our addition server in the Slug lazy language, using its IO encoding.

To do this, consider the continuation-based web reading function from class (only using read-line instead of read):

(define (web-read/k prompt k)
  (set-box! what-next
            (lambda ()
              (printf "~a: " prompt)
              (k (read-line))))
  (error 'web-read/k "enter (resume) to continue"))
and the implementation of reading in the Slug language — taken from the solution, and omitting the types for simplicity:
(define (execute-read receiver)
  (execute-receiver receiver read-line))
(define (execute-receiver receiver producer)
  (cases receiver
    [(FunV names body env)
     (execute-val
      (eval body (extend names (list (wrap-in-val (producer))) env)))]
    [else (error 'execute-receiver "expecting a receiver function")]))
To further simplify things, we’ll merge the two to make an implementation for reading without the execute-receiver helper:
(define (execute-read receiver)
  (cases receiver
    [(FunV names body env)
     (execute-val
      (eval body (extend names (list (wrap-in-val (read-line))) env)))]
    [else (error 'execute-receiver "expecting a receiver function")]))

Now, to demonstrate the similarity, you need to show how execute-read can be modified so it works in a stateless environment such as a web server. Re-write it, assuming that you have the same what-next global box. You can ignore the prompt for this question.



 

Question 5: Continuations and Macros Part I
 16 pts 


When we discussed continuations, we have implemented a DSL that has continuations by a CPS transformation. We did this formally via a CPS macro that did the transformation, and a CPS-code macro that used it to convert a complete program. (See CPS-LANGUAGE for these macros.)

One feature that is obviously missing is a conditional. Extend this DSL by adding a conversion rule for if expressions.



 

Question 6: Macros and Continuations Part II
 18 pts 


Another important feature that is missing from our CPS language is functions of any arity. Dealing with functions of any arity is going to be difficult, so instead, you’ll just extend the language to allow functions (and function calls) of two arguments in addition to unary ones. Note that these are still just user-defined functions and calls to them.

You need to do three things:

  1. Add a case to CPS-code that allows definitions of binary functions.
  2. Add a case to CPS to handle two arguments in lambda expressions.
  3. Add a case to CPS to handle two-argument function applications.

Note that where you add the new cases in the two macros can be important, so for a solution, you should write the full definitions of the two macros. Don’t forget that this is an extension that is added to the one from the last question, so make sure that your answer includes your if case too.



 

Question 7: Language Features
 18 pts 


In the following questions choose the single best answer (unless told otherwise).

  1. Does it make sense to have a language that is dynamically scoped and statically typed?
    1. No, there is no way to do such static type checking since bindings of identifiers are determined dynamically, and there is no way to know the values that they will get without actually evaluating the code.
    2. It does not make sense to try that unless we use a significantly more complicated type checker that can infer how values will flow during the dynamic execution of the code in question.
    3. Yes, static typing is always beneficial, even more so in a dynamically scoped language where it guarantees that we can get some level of reliability.
    4. No, since as we’ve seen dynamically scoped languages can always implement recursive functions and therefore loops, but a static type checker leads to a strongly normalizing language where such loops are impossible.
  2. Our CPS-code program transformation processes each definition individually. Is this a problem for defining mutually recursive functions? To make a simplified example (with just a forward reference), when we enter:
    (CPS-code (define (f x) (g x))
              (define (g x) (+ 10 x))
              (f 1))
    will we run into a problem?
    1. Yes, we’ve seen that the CPS transformation is similar to how we deal with side-effects in lazy evaluation, so if CPS-code processes each definition individually, it will be like trying to force a value for one binding that refers to the yet-undefined value of the next one.
    2. Yes, the CPS transformation is essentially determining the order of evaluation so it is close to evaluation, which means that we will run into undefined values which will break with the macro expansion containing an undefined identifier.
    3. Yes, transforming a pair of mutually recursive functions will lead the macro expansion into an infinite loop, similar to what we’ve seen with the badly implemented while macro.
    4. No, while the CPS transformation is close to evaluation, we don’t get an unbound identifier because the bad reference is inside a lambda so it’s effectively delayed.
    5. No, the macro is simply transforming code, and the future reference is treated as a plain identifier, which the macro leaves as is, and the define magic in Racket will make the recursive reference work as it usually does.
  3. In question 6 there was a comment that said
    Dealing with functions of any arity is going to be difficult [...]
    What makes it difficult?
    In this question you can choose any number of items. (There’s only one combination that is a correct answer.)
    1. The extension that we would need to do to the CPS-code macro would be difficult.
    2. The extension that we would need to do to support any-arity function application in the CPS macro would be difficult.
    3. The extension that we would need to do to support any-arity lambda expressions in the CPS macro would be difficult.