Instructions
- Deadline: Friday March 1 at 11:59PM
- Starter code: here
- This quiz is to be completed on your own. Please do not consult with classmates. You are permitted to use the course notes and Dr Racket. Only ask questions relating to the quiz as private Piazza questions.
- Upload a single file called
code.rkt
to Gradescope. Your upload should be valid Plait: be sure your upload runs in Dr Racket without errors. Use the provided starter code. - You should consult primarily with the course notes and course textbook while answering questions on this quiz.
- Note: The autograder score is not your final score.
- The quiz is graded out of 100 points in total.
Problem 1: Short answer & multiple choice [30 Points]
Problem 1a [9 Points]
Consider the following Plait AST and interpreter for a small language with let-bindings, addition, numbers, if, and Booleans:
(define-type TinyExpr
[boolTE (b : Boolean)]
[numTE (n : Number)]
[varTE (v : Symbol)]
[addTE (e1 : TinyExpr) (e2 : TinyExpr)]
[ifTE (guard : TinyExpr) (thn : TinyExpr) (els : TinyExpr)]
[let1TE (id : Symbol) (e1 : TinyExpr) (e2 : TinyExpr)])
; substitute e1[x |-> e2]
(subst : (TinyExpr Symbol TinyExpr -> TinyExpr))
(define (subst e1 x e2)
(type-case TinyExpr e1
[(varTE s) (if (symbol=? s x)
e2
(varTE s))]
[(numTE n) (numTE n)]
[(addTE add1 add2) (addTE (subst add1 x e2) (subst add2 x e2))]
[(ifTE g thn els) (ifTE (subst g x e2) (subst thn x e2) (subst els x e2))]
[(boolTE b) (boolTE b)]
[(let1TE id assgn body)
(if (symbol=? x id)
(let1TE id e1 e2) ; shadowing case
(let1TE id (subst assgn x e2) (subst body x e2)))]
))
; interpret expressions
(interp : (TinyExpr -> TinyExpr))
(define (interp e)
(type-case TinyExpr e
[(varTE s) (error 'runtime "unrecognized symbol")]
[(numTE n) (numTE n)]
[(boolTE b) (boolTE b)]
[(addTE e1 e2) (numTE (+ (numTE-n (interp e1)) (numTE-n (interp e2))))]
[(ifTE g thn els)
(if (boolTE-b (interp g))
(interp thn)
(interp els))]
[(let1TE x e1 e2)
(interp (subst e2 x (interp e1)))]))
We design the following type-system for this language:
\[\dfrac{\texttt{Γ}(x) = \texttt{τ}}{\texttt{Γ ⊢ x : τ}}(\text{T-var})\] \[\dfrac{}{\texttt{Γ ⊢ num : Number}}(\text{T-Num}) \quad\quad \dfrac{}{\texttt{Γ ⊢ true : Bool}}(\text{T-true}) \quad\quad \dfrac{}{\texttt{Γ ⊢ false : Bool}}(\text{T-false}) \quad\quad\] \[\dfrac{\texttt{Γ ⊢ e1 : Bool} \quad\quad \texttt{Γ ⊢ e2 : τ} \quad\quad \texttt{Γ ⊢ e3 : τ}} {\texttt{Γ ⊢ (if e1 e2 e3) : τ}}(\text{T-If})\] \[\dfrac{\texttt{Γ ⊢ e1 : Number} \quad\quad \texttt{Γ ⊢ e2 : Number}} {\texttt{Γ ⊢ (+ e1 e2) : Number}}~\text{(T-Add)}\] \[\dfrac{\texttt{Γ ⊢ e1} : \texttt{τ1} \quad \quad \texttt{Γ} ∪ \texttt{\{x ↦ τ1\}} \vdash \texttt{e2} : \texttt{τ2} } {\texttt{Γ ⊢ (let (x : τ1 e1) e2) : τ2}}(\text{T-Let})\]For each of the following derivation trees, state whether the tree is valid or invalid according to the typing rules above. 3 points of credit are given for each correctly marked answer. Some rule names are omitted for space; this does not invalidate the typing rule.
- \[\dfrac{\dfrac{}{\texttt{\{\} ⊢ 1 : Number}}~\text{(T-Num)} \quad\quad \dfrac{}{\texttt{\{\} ⊢ 2 : Number}}~\text{(T-Num)}} {\texttt{\{\} ⊢ (+ 1 2) : Number}}~\text{(T-Add)}\]
- \[\dfrac{\dfrac{}{\{\} ⊢ \texttt{true : Bool}} \quad \dfrac{}{\{\} ⊢ \texttt{10 : Number}} \quad \dfrac{}{\{\} ⊢ \texttt{false : Bool}} }{\{\} ⊢ \texttt{(if true 10 false) : Number}}~\text{(T-If)}\]
- \[\dfrac{\dfrac{}{\texttt{\{\} ⊢ x : Bool}}}{\texttt{\{\} ⊢ (let (x : Bool true) x) : Bool}}~\text{(T-Let)}\]
Problem 1b [6 Points]
Continuing with the TinyExpr
language above, define a term called problem-1b
of type TinyExpr
that is not well-typed and does not cause the interpreter to have a runtime error.
No partial credit will be given on this problem.
Problem 1c [5 Points]
Suppose you are using the following alternative type-system for TinyExpr
, where all of the rules are the same except for T-Let which is now the following:
Define a term problem-1c
of type TinyExpr
that is well-typed according to these new rules, but that causes a run-time error in the above interpreter.
No partial credit will be given on this problem.
Problem 1d [10 Points, 2.5 Points each]
Mark each of the following as true or false (no partial credit):
- Adding functions to our
TinyExpr
grammar is a change to the host semantics. - The fact that
TinyExpr
numbers can be floating point, integer-valued, or rational-valued is an example of host semantics. - All type checkers always prevent all possible runtime errors.
- $\Omega$ can be typed in the simply-typed $\lambda$-calculus.
Problem 2: Designing a type-system [70 Points]
In this problem, we will be designing an interpreter and type-checker for an imperative language. Imperative languages look different than the kinds of programming languages we’ve been working with so far: their syntax consists of expressions and statements. Examples of imperative languages include Python and Java. Here is the abstract syntax for our tiny imperative language:
(define-type ImpExp
[varE (v : Symbol)]
[numE (n : Number)]
[boolE (b : Boolean)]
[ifE (g : ImpExp) (thn : ImpExp) (els : ImpExp)]
[addE (e1 : ImpExp) (e2 : ImpExp)])
(define-type ImpStatement
[seqS (s1 : ImpStatement) (s2 : ImpStatement)]
[assignS (var : Symbol) (assignment : ImpExp)])
(define-type ImpProgram
[prog (p : ImpStatement) (ret : ImpExp)])
(define-type Value
[VBool (b : Boolean)]
[VNum (n : Number)])
For example, the following ImpProgram
:
(prog
(seqS
(assignS 'x (numE 2))
(assignS 'x (addE (varE 'x) (numE 10))))
(varE 'x))
can be translated syntactically into the equivalent Python program.
x = 2
x = x + 10
x
Note: 5 points of your score (out of the 70 for Problem 2) will be style. Your solution should make use of type-case
instead of cond
, should contain type annotations for all functions, should have a comment describing the purpose of each function, and should have at least 2 test cases for each defined function. We will deduct 1 point from your style score for each missing component.
Problem 2a: An imperative interpreter, part 1 [15 Points]
In this problem, you will implement an interpreter interp-e
of type (Env ImpExp -> Value)
that evaluates ImpExp
s. The type Env
is a mutable environment; a basic interface to manipulate environments is given for you in the starter code. Additionally we have given you two functions value->int
and value->bool
that extracts the Number
s and Bool
s bound in an expression of type Value
, respectively.
ImpExp
consists of variable references (varE
), number constants (numE
), Boolean constants (boolE
), if-expressions ifE
, and addition addE
. The semantics of these expressions is identical to that of the languages we have seen in class.
ifE g thn els
should raise a'runtime
error ifg
does not evaluate to aVBool
.addE e1 e2
should raise a'runtime
error if either of its arguments do not evaluate toVNum
.varE s
should raise a'runtime
error ifs
is not found inEnv
.
Example:
>(interp-e
(box (hash (list (pair 'x (VNum 5)))))
(addE (varE 'x) (numE 10)))
(VNum 15)
We will evaluate your solution based on the fraction of tests that pass.
Problem 2b: An imperative interpreter, part 2 [15 Points]
In this problem, you will implement an interpreter interp-p
of type (ImpProgram -> Value)
that evaluates ImpProgram
s. You will likely need a helper function.
- The semantics of an
ImpStatement
consists of the following semantics:seqS s1 s2
evalautess1
and then evaluatess2
in sequence.assignS var assignment
assigns variablevar
equal to the result of evaluatingassignment
. Ifvar
is already assigned, then it is updated to be the new result.
- The semantics of an
ImpProgram
, which will have the form(prog p ret)
, is to (1) runp
to get an environment containing assignments to variables, and (2) return the result of runningret
in the environment obtained from (1).
Example:
>(interp-p (prog (seqS
(assignS 'x (numE 5))
(assignS 'x (addE (varE 'x) (numE 10))))
(varE 'x)))
(VNum 15)
We will evaluate your solution based on the fraction of tests that pass.
Problem 2c: An imperative typechecker [35 Points]
Now, we will make a type-checker for our imperative language so that well-typed programs do not raise 'runtime
errors. We will use the following Type
datatype:
(define-type Type
[TNum]
[TBool])
Make a function type-of-p p
of type (ImpProgram -> Type)
that gives the type of the returned value from the program and raises a 'type-error
if p
would cause a runtime error for the interpreter in Part 2a.
Example:
> (type-of-p (prog (assignS 'x (numE 10)) (varE 'x)))
(TNum)
> (type-of-p (prog (assignS 'x (numE 10)) (varE 'y)))
; raises 'type-error
We will evaluate your solution based on the total fraction of tests that pass from two categories of tests:
- Tests that check that any program that can raise a
'runtime
error for the interpreter in Part 2a raise'type-error
s when type-checked. Tests that check that valid programs (i.e., programs that do not cause runtime errors) do not raise
'type-error
s.Note: Just like we’ve seen in class, your solution should raise a type error for programs where
ifE
would return different types for the two branches, i.e.(ifE (boolE #t) (numE 10) (boolE #f))
should cause a'type-error
. In all other cases, if running the program in the interpreter does not cause a'runtime
erorr, then your type-checker should not raise a'type-error
.