AdministrativeThis exam was given in the Spring semester of 2011. Note that in this exam the “Type System” and “Continuations” questions are relatively easy, since we went over the material in less details than in this semester. |
| |||||||||||||||||||||||||
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.
We want to implement a concept of “simple function calls”, where you apply a function in a way that forbids calling it again during the call. (This can be useful, for example, to forbid recursive calls in cases where you want to avoid infinite loops.)
To do this, we will use boxes that hold a unary function, and change the box when the function is called. This will be done by a simple-call function that recieves the boxed function and an appropriate argument. Implement this function. (Don’t forget to write a proper type.)
Here are two simple examples to demonstrate how it works:
(: add1 : (Boxof (Number -> Number))) (define add1 (box (lambda (n) (+ n 1)))) (test (simple-call add1 9) => 10) (: bad : (Boxof (Number -> Number))) (define bad (box (lambda (n) (simple-call bad (+ n 1))))) (test (simple-call bad 9) =error> "recursive calls forbidden") |
Throughout the semester, we have implemented a number of languages. The implementation strategy for all of these languages was a definition of an interpreter. An alternative strategy that would be useful is one that we’ve discussed in Lecture #21 and Lecture #22 is to define a new language by translating it into Racket via its macro system, which has one advantage of a simpler implementation (since it borrows more from Racket) and more functionality (since it can by used as an extension to Racket, so the full language is still there). Obviously, the fact that Racket is used to provide the semantics, it wouldn’t be as useful for learning how to implement such languages. But it’s still useful to do this to get some of Racket’s advantages, for example DrRacket’s syntax checker, or Racket’s speed.
Implement a Flang-like language on top of Racket via a single FLANG macro that serves as a kind of a Flang compiler. The macro will do the following:
(FLANG {with {add {fun {x} {fun {y} {+ x y}}}}
{call {call add 3} 4}}) |
(let ([add (lambda (x) (lambda (y) (+ x y)))]) ((add 3) 4)) |
Your macro should handle the complete language. Here is the complete BNF that it should compile:
<FLANG> ::= <num>
| { + <FLANG> <FLANG> }
| { - <FLANG> <FLANG> }
| { * <FLANG> <FLANG> }
| { / <FLANG> <FLANG> }
| { with { <id> <FLANG> } <FLANG> }
| { rec { <id> <FLANG> } <FLANG> }
| <id>
| { fun { <id> } <FLANG> }
| { call <FLANG> <FLANG> } |
Implementation notes:
As we have seen, cond in the lazy language (#lang pl lazy, not Schlac) that we have used in class was still a special form (a macro). The reason for that is a technicality: it has a syntax that is different from functions — for example, if it was a function then something like this:
(cond [(> x 0) "positive"]
[(< x 0) "negative"]
[else "zero"]) |
Such a cond function would be used a little differently:
(cond (list (list (> x 0) "positive")
(list (< x 0) "negative")
(list else "zero"))) |
If it’s possible to write such a function, then do so. Remember that you need to deal with else too — and it should throw an error if no clauses succeed (which never happens if there’s an else clause, of course). Also, remember that we didn’t have types in the lazy language, so write an informal contract in a comment for your definition(s).
If it can’t be done, then you need to explain (shortly!) why it is not possible.
In the following questions choose the single best answer (unless told otherwise).
(lambda (something?)
(automaton start end
[start : x -> (if something? state1 state2)]
[state1 : ...]
[state2 : ...]
[end : ...])) |
In the last lecture, we have seen some translations of code to continuation passing style. We wish to implement a “web app” that reads numbers in a loop, stopping as soon as the numbers sum up to a positive number. This application is initiated with a positive-sum/k function, and the loop is implemented by a positive-loop/k function. These would be the continuation passing style translation of the following code:
(define (positive-sum) ; entry point
(positive-loop 0))
(define (positive-loop sum)
(let ([sum (+ sum (web-read "A number"))])
(if (> sum 0)
sum
(positive-loop sum)))) |
To make things easier, here is a skeleton code for you to fill in:
(define (positive-loop/k sum k)
(web-read/k "A number"
...fill-in...))
(define (positive-sum/k k)
(positive-loop/k 0 k)) |
> (positive-sum/k web-display) web-read/k: enter (resume) to continue > (resume) A number: -1 web-read/k: enter (resume) to continue > (resume) A number: -10 web-read/k: enter (resume) to continue > (resume) A number: 5 web-read/k: enter (resume) to continue > (resume) A number: 100 web-output: 94 |
In the exam that was given last year (the third one) the Picky type system was extended by separating the Number type into Integer and Float, with the notion of the first being a subtype of the second, and using automatic coercion when mixed number types are used. To summarize the changes that this lead to:
For reference, here is the extended BNF and the new type judgments:
<PICKY> ::= <int>
| <fpnum>
| <id>
| { fun { <id> : <TYPE> } : <TYPE> <PICKY> }
| { with { <id> : <TYPE> <PICKY> } <PICKY> }
| { rec { <id> : <TYPE> <PICKY> } <PICKY> }
| { + <PICKY> <PICKY> }
| { < <PICKY> <PICKY> }
| { call <PICKY> <PICKY> }
| { if <PICKY> <PICKY> <PICKY> }
<TYPE> ::= Integer
| Float
| Boolean
| ( <TYPE> -> <TYPE> )
Γ ⊢ i : Integer ; <--- i is for all literal integers
Γ ⊢ fp : Float ; <--- fp is for floating point numbers
Γ ⊢ x : Γ(x)
Γ ⊢ E : Integer
——————————————— ; <--- the coercion rule
Γ ⊢ E : Float
Γ ⊢ a : Integer Γ ⊢ b : Integer
—————————————————————————————————
Γ ⊢ {+ a b} : Integer
Γ ⊢ a : Float Γ ⊢ b : Float
—————————————————————————————
Γ ⊢ {+ a b} : Float
Γ ⊢ a : Integer Γ ⊢ b : Integer
—————————————————————————————————
Γ ⊢ {< a b} : Boolean
Γ ⊢ a : Float Γ ⊢ b : Float
—————————————————————————————
Γ ⊢ {< a b} : Boolean
Γ[x:=τ1] ⊢ E : τ2
——————————————————————————————————————
Γ ⊢ {fun {x : τ1} : τ2 E} : (τ1 -> τ2)
Γ ⊢ f : (τ1 -> τ2) Γ ⊢ v : τ1
——————————————————————————————
Γ ⊢ {call f v} : τ2
Γ ⊢ c : Boolean Γ ⊢ t : τ Γ ⊢ e : τ
———————————————————————————————————————
Γ ⊢ {if c t e} : τ
Γ ⊢ v : τ1 Γ[x:=τ1] ⊢ E : τ2
——————————————————————————————
Γ ⊢ {with {x : τ1 v} E} : τ2
Γ[x:=τ2] ⊢ E : τ Γ[x:=τ2] ⊢ v : τ2
————————————————————————————————————
Γ ⊢ {rec {x : τ2 v} E} : τ |
The reason that there’s no need for additional numeric rules for + and < is that when needed, the coercion rule can be used to complete a proof, and that represents automatic coercion that the language will do. Here’s an example that requires this, since the two inputs have different number types:
Γ ⊢ 1 : Integer
———————————————
Γ ⊢ 1 : Float Γ ⊢ 2.3 : Float
———————————————————————————————
Γ ⊢ {+ 1 2.3} : Float |
This language works fine, but one thing is that we can’t see the subtype relation between the two function types. For example, a function type that returns an Integer can be considered as one that returns a Float.