0% found this document useful (0 votes)
6 views9 pages

21 1 Midterm

The document is a midterm exam for CS 320, Spring 2021, consisting of 10 questions across 9 pages, with instructions for answering in Korean and/or English. It covers various programming language concepts, including semantics, closures, scoping strategies, and the implementation of interpreters. The exam also includes specific tasks such as filling in blanks, writing expressions, and explaining concepts related to programming languages.

Uploaded by

전성진
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views9 pages

21 1 Midterm

The document is a midterm exam for CS 320, Spring 2021, consisting of 10 questions across 9 pages, with instructions for answering in Korean and/or English. It covers various programming language concepts, including semantics, closures, scoping strategies, and the implementation of interpreters. The exam also includes specific tasks such as filling in blanks, writing expressions, and explaining concepts related to programming languages.

Uploaded by

전성진
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

Midterm Exam

CS 320, Spring 2021

Instructions

• You have 105 minutes to complete this closed-book exam.


• There are 9 pages and 10 questions.

• You should write answers in Korean and/or English.


• You cannot ask any questions. If you think that a question has a defect, explain it and then solve the
question after revising the question not to have the defect.
• You cannot go to a toilet.

• n ranges over integers (n ∈ Z).


• x ranges over identifiers (x ∈ Id ).
• e ranges over expressions (e ∈ Expr ).
• v ranges over values (v ∈ Val ).

• a ranges over addresses (a ∈ Addr ).


fin fin
• σ ranges over environments (σ ∈ Id −→ Val in FAE and RFAE, and σ ∈ Id −→ Addr in MFAE).
fin
• M ranges over stores (M ∈ Addr −→ Val ).

• We use parentheses to represent expressions clearly. For example, we may write λx.(x + x) instead of
λx.x + x to make sure that we do not mean (λx.x) + x. You can use parentheses in your answers.
• We often omit the terms “evaluation” and “evaluating” for brevity. For example, “the result of e is v”
means that the result of evaluating e is v, and “e terminates” means that evaluation of e terminates.

• If a question says “write the result of e,” then


– you should write v when e results in v.
– you should write “not terminate” when e does not terminate.
– you should write “error” when e incurs a run-time error.

• When you write expressions in your answers, you can freely choose between concrete syntax and
abstract syntax notations to write the expressions. For example, both { x => x } and λx.x are
possible.
• There is an appendix at page 9. The appendix shows the definitions of FAE, RFAE, and MFAE. If you
need the definitions to solve a question, refer to the appendix.

1
1) (10pts) The following sentences explain basic concepts in programming languages.

• A(n) (A) takes a program and produces a result.


• A(n) (B) takes a program and produces a program. gcc, javac, and scalac are well-known
examples.
• A(n) (C) takes a string representing a program and produces a tree representing a program.
• A variable definition consists of “val”, a string that is an identifier, “=”, and a string that is an
expression. This describes the (D) of a variable definition.
• A variable definition consists of an identifier and an expression. This describes the (E) of a
variable definition.
• A variable definition evaluates the expression and add a mapping from the identifier to the result
of the expression to the current environment. This describes the (F) of a variable definition.
• In a language with (G) functions, functions can be used as values. However, in a language
that supports only (H) functions, functions are not values.
• A(n) (I) , which knows what each identifier denotes, is used for binding. A(n) (J) , which
knows what each memory location contains, is used for mutation.

Fill from (A) to (J) with the following terms. (1pt each)

concrete syntax interpreter first-order abstract syntax semantics


store parser first-class environment compiler

2) (10pts) This question asks you why we need closures. Consider the following semantics without
closures:
σ ` e1 ⇒ λx.e σ ` e2 ⇒ v 2 [x 7→ v2 ] ` e ⇒ v
v ::= n | λx.e σ ` λx.e ⇒ λx.e
σ ` e1 e2 ⇒ v

a) (5pts) Explain why the above semantics, which lacks closures, is problematic in one sentence.
Your explanation must include the terms “free identifier” and “environment”.
b) (5pts) Write an FAE expression such that
• the evaluation in the original semantics with closures results in an integer, but
• the evaluation in the above semantics without closures incurs a run-time error.

2
3) (5pts) Consider the following RFAE expression:
{
val f = { x =>
1 2
if0 (x) {
3
0
} else {
f((x - 1))
4 5
}
};
{
def f(x) =
6 7
if0 (x) {
8
0
} else {
f((x - 1))
9 10
};
f(y)
11 12
}
}
(The numbers below the identifiers are not parts of the expression. They are for convenience of writing
answers. Use the numbers to denote identifiers in your answers.)

a) (3pts) Write every bound occurrence and its binding occurrence. (To say that x in line 2 is a
bound occurrence whose binding occurrence is f in line 2, write 2->1 in your answer.)
b) (2pts) Write every free identifier. (To say that f in line 2 is a free identifier, write 1 in your
answer.)

4) (5pts) Consider the following RFAE expression:

{
def f(n) = if0 (n) { 0 } else { (n + f((n - 1))) };
{
val f = { x => (10 + f(x)) };
f(4)
}
}

What is the result of this expression under each of the following scoping strategies?
a) (2pts) Static scoping
b) (3pts) Dynamic scoping

3
5) (10pts) The following code is an excerpt from the implementation of an interpreter for MFAE:

def interp(expr: Expr, env: Env, sto: Sto): (Value, Sto) = expr match {
...
case App(f, a) => a match {
case Id(name) =>
val (fv, fs) = interp(f, env, sto)
fv match {
case CloV(x, b, fenv) =>
val addr = lookup(name, env)
interp(b, fenv + (x -> addr), fs)
case _ => error("not a closure")
}
case _ =>
val (fv, fs) = interp(f, env, sto)
val (av, as) = interp(a, env, fs)
fv match {
case CloV(x, b, fenv) =>
val addr = malloc(as)
interp(b, fenv + (x -> addr), as + (addr -> av))
case _ => error("not a closure")
}
}
}

a) (2pts) What is this semantics? Choose one: call-by-value or call-by-reference.

For the following subquestions, you can use any notation you prefer in your answers. For example, you
may write [a1 -> 3, a2 -> <{x => x}, []>] to denote the store that maps an address a1 to the
integer 3 and an address a2 to the closure hλx.x, ∅i. If you think your notation is confusing, describe
what your notation means in the answer.
Consider the following MFAE expression:

{
val n = 42;
{
val f = { g => g(n) };
f({ x => (x + 8) })
}
}

b) (4pts) Write the environment and store just before evaluating (x + 8) in the call-by-value se-
mantics.
c) (4pts) Write the environment and store just before evaluating (x + 8) in the call-by-reference
semantics.

4
6) (5pts) Consider the function f in the following FAE expression:
1 {
2 val f = {
3 val fX = { fY => {
4 val f = fY(fY);
5 { n => if0 (n) { 0 } else { (n + f((n - 1))) } }
6 }};
7 fX(fX)
8 };
9 f(10)
10 } (
0 if n = 0
Also, consider the following mathematical function: f (n) =
n + f (n − 1) if n > 0
Is the FAE function the same as the mathematical function? If not, fix the expression to make them
equal. You do not need to justify your answer when it is “yes”. (You can use line numbers to write
your answer.)

7) (10pts) Mutually recursive functions are functions that call each other. For example, the following
Scala code defines two mutually recursive functions even and odd:

def even(n: Int): Boolean = if (n == 0) true else odd(n - 1)


def odd(n: Int): Boolean = if (n == 0) false else even(n - 1)

The result of even(10) is true, and the result of odd(10) is false.


In the lecture, we implemented recursive functions in FAE, which does not support recursion. In a
similar way, we can also implement mutually recursive functions in FAE. For this purpose, we add pairs
to FAE: (e1 , e2 ) creates a new pair; e._1 gives the first value of a pair; e._2 gives the second value of
a pair. Also, in order to implement even and odd, we add booleans to FAE: true and false.
The following expression implements even and odd in FAE:
{
val eo = {
val eoX = { eoY => {
val even = (A) ;
{
val odd = (B) ;
( (C) , (D) )
}
}};
eoX(eoX)
};
{
val even = eo._1;
{
val odd = eo._2;
(even(10), odd(10))
}
}
}
Fill the blanks to complete the expression. The result of the expression should be (true, false).
(Hint: eoX is a function that returns a pair of functions.)

5
8) (15pts) Below is an interpreter for FAE written in Scala. Unlike the interpreters in the lectures, the
following interpreter uses substitutions, instead of environments. (For simplicity, the implementation
omits some expressions.)
sealed trait Expr
sealed trait Value extends Expr
case class Num(value: Int) extends Expr with Value
case class Id(x: String) extends Expr
case class Fun(x: String, body: Expr) extends Expr with Value
case class App(fun: Expr, arg: Expr) extends Expr

def subst(expr: Expr, x: String, e: Expr): Expr = expr match {


case Num(n) => expr
case Id(y) => if (y == x) e else expr
case Fun(y, b) => Fun(y, if (y == x) b else subst(b, x, e))
case App(f, a) => App(subst(f, x, e), subst(a, x, e))
}

def interp(expr: Expr): Value = expr match {


case Num(n) => Num(n)
case Id(_) => error("free identifier")
case Fun(x, b) => Fun(x, b)
case App(f, a) => interp(f) match {
case Fun(x, b) => interp(subst(b, x, interp(a)))
case _ => error("not a function")
}
}
Note that the definition of a value has been changed: v ::= n | λx.e
Now, Val is a subset of Expr . In the code, “sealed trait Value extends Expr” at line 2 implies
that if v is a Value, then v is also an Expr at the same time. In addition, because of “extends Expr
with Value” at lines 3 and 5, Num(n) and Fun(x, b) are Values and are Exprs at the same time.
Let us call the original semantics of FAE (with environments) Senv and this new semantics (with
substitutions) Ssubst . We say that Ssubst is equivalent to Senv when e evaluates to n in Ssubst if and
only if e evaluates to n in Senv . Surprisingly, Ssubst is not equivalent to Senv .

a) (5pts) Write an FAE expression such that


• the evaluation in Ssubst results in an integer, but
• the evaluation in Senv incurs a run-time error.
(Hint: Consider an expression containing a free identifier.)
b) (10pts) Fix the interpreter to make Ssubst equivalent to Senv . (Hint: Fix the Fun case of the
funtion subst.)
You may use the following helper functions in your answer without defining them:
// returns the set of every binding identifier in e
def binding(e: Expr): Set[String]
// returns the set of every free identifier in e
def free(e: Expr): Set[String]
// returns a new identifier that does not belong to xs
def fresh(xs: Set[String]): String
Also, xs1 ++ xs2 is the union of xs1 and xs2 where both xs1 and xs2 are sets, and Set(x) is a
set whose only element is x.

6
9) (15pts) This question extends LFAE with val, if0, and pairs. Consider the following interpreter
implementation:

sealed trait Expr


case class Num(n: Int) extends Expr
case class Add(l: Expr, r: Expr) extends Expr
case class Id(x: String) extends Expr
case class Fun(x: String, b: Expr) extends Expr
case class App(f: Expr, a: Expr) extends Expr
case class Val(x: String, e: Expr, b: Expr) extends Expr
case class If0(c: Expr, t: Expr, f: Expr) extends Expr
case class Pair(f: Expr, s: Expr) extends Expr
case class Fst(e: Expr) extends Expr
case class Snd(e: Expr) extends Expr

sealed trait Value


case class NumV(n: Int) extends Value
case class CloV(p: String, b: Expr, e: Env) extends Value
case class ExprV(e: Expr, env: Env) extends Value
case class PairV( (A) ) extends Value

type Env = Map[String, Value]

def strict(v: Value): Value = v match {


case ExprV(e, env) => strict(interp(e, env))
case _ => v
}

def interp(e: Expr, env: Env): Value = e match {


case Num(n) => NumV(n)
case Add(l, r) => (strict(interp(l, env)), strict(interp(r, env))) match {
case (NumV(x), NumV(y)) => NumV(x + y)
case (x, y) => error("not a number")
}
case Id(x) => env.getOrElse(x, error("free identifier"))
case Fun(x, b) => CloV(x, b, env)
case App(f, a) =>
interp(f, env) match {
case CloV(x, b, fenv) => interp(b, fenv + (x -> ExprV(a, env)))
case v => error("not a function")
}
case Val(x, e, b) => (B)
case If0(c, t, f) => (C)
case Pair(f, s) => (D)
case Fst(e) => (E)
case Snd(e) => (F)
}

(The question continues on the next page.)

7
a) (3pts) Like in FAE, the semantics of val x = e1 ; e2 must be the same as (λx.e2 ) e1 . Fill (B).
b) (3pts) Like in RFAE, if0 e1 e2 e3 evaluates e2 when e1 evaluates to 0 and evaluates e3 when e1
evaluates to a nonzero integer, a closure, or a pair. Fill (C).
c) (9pts) The semantics of pairs is as follows:
• Pair(e1 , e2 ) corresponds to (e1 , e2 ), which is an expression that creates a new pair.
• Fst(e) corresponds to e._1, which is an expression that gives the first value of a given pair.
• Snd(e) corresponds to e._2, which is an expression that gives the second value of a given pair.
Pairs in this language are lazy, which means that e1 and e2 in (e1 , e2 ) are not evaluated when
the pair is created. Each of them is evaluated only when its value is needed. For example,
• (3, ({ x => x } + 4)) does not incur a run-time error.
• ((3, ({ x => x } + 4))._1 + 5) does not incur a run-time error.
• ((3, ({ x => x } + 4))._2 + 5) incurs a run-time error.
Fill (A), (D), (E), and (F). You can put any number of fields in (A).
10) (15pts) This question extends FAE with exceptions.
Expression e ::= n | e + e | x | λx.e | e e | throw | try e catch e
Value v ::= n | hλx.e, σi
Result r ::= v | exc
Due to the presence of exceptions, an expression can throw an exception instead of evaluating to a
value. σ ` e ⇒ v denotes that e evaluates to v under σ, and σ ` e ⇒ exc denotes that e throws an
exception under σ.
Note that errors are different from exceptions. Errors are unintended failures of evaluation, which
cannot be handled. For example, adding an interger to a closure causes an error and terminates the
execution immediately. However, exceptions are thrown by throw written by programmers and can
be handled by try-catch. Therefore, exceptions are thrown and handled according to the intention of
programmers.
The semantics of the language is as follows:
• throw throws an exception.
• Any expressions except try-catch propagate exceptions, i.e. if a subexpression of e that is not
try-catch throws an exception, then e also throws an exception without evaluating the remaining
subexpressions. This language uses the left-to-right order for evaluating subexpressions.
• During the evaluation of each expression, dynamic type checking of the values of the subexpres-
sions happens only after evaluating all the subexpressions. For example, in e1 + e2 , when e1
evaluates to v1 , it is checked whether v1 is an integer or not after the evaluation of e2 .
• try e1 catch e2 handles an exception. If e1 throws an exception, try e1 catch e2 evaluates e2 and
uses the result of e2 as its result. If e1 does not throw an exception, the result of e1 is used as the
result without evaluating e2 .
Below are some examples:
• ∅ ` throw ⇒ exc
• ∅ ` throw + 1 ⇒ exc
• ∅ ` (λx.x) + throw ⇒ exc
• ∅ ` throw + ((λx.(x x)) (λx.(x x))) ⇒ exc
• ((λx.(x x)) (λx.(x x))) + throw does not terminate.
• ∅ ` try 1 catch 2 ⇒ 1
• ∅ ` try throw catch 2 ⇒ 2
a) (10pts) Write the operational semantics of the form σ ` e ⇒ r .
b) (5pts) Draw the evaluation derivation of try (1 + throw) catch (throw + 2).

8
Appendix
• FAE
– Concrete syntax
expr ::= num | "(" expr "+" expr ")" | "(" expr "-" expr ")" | id
| "{" "val" id "=" expr ";" expr "}" | "{" id "=>" expr "}"
| expr "(" expr ")" | "if0" "(" expr ")" "{" expr "}" "else" "{" expr "}"
– Abstract syntax
e ::= n | e + e | e − e | x | val x = e; e | λx.e | e e | if0 e e e
v ::= n | hλx.e, σi
– Semantics
σ ` e1 ⇒ n1 σ ` e2 ⇒ n2 σ ` e1 ⇒ n1 σ ` e2 ⇒ n2
σ`n⇒n
σ ` e1 + e2 ⇒ n1 + n2 σ ` e1 − e2 ⇒ n1 − n2

x ∈ Domain(σ) σ ` e 1 ⇒ v1 σ[x 7→ v1 ] ` e2 ⇒ v2
σ ` x ⇒ σ(x) σ ` val x = e1 ; e2 ⇒ v2

σ ` e1 ⇒ hλx.e, σ 0 i σ ` e 2 ⇒ v2 σ 0 [x 7→ v2 ] ` e ⇒ v
σ ` λx.e ⇒ hλx.e, σi
σ ` e1 e2 ⇒ v

σ ` e1 ⇒ 0 σ ` e 2 ⇒ v2 σ ` e 1 ⇒ v1 v1 6= 0 σ ` e 3 ⇒ v3
σ ` if0 e1 e2 e3 ⇒ v2 σ ` if0 e1 e2 e3 ⇒ v3
• RFAE (The omitted parts are the same as FAE.)
– Concrete syntax
e ::= ... | "{" "def" id "(" id ")" "=" expr ";" expr "}"
– Abstract syntax
e ::= · · · | def x(x) = e; e
– Semantics
σ 0 = σ[x1 7→ hλx2 .e1 , σ 0 i] σ 0 ` e 2 ⇒ v2
σ ` def x1 (x2 ) = e1 ; e2 ⇒ v2
• MFAE
– Concrete syntax (The omitted parts are the same as FAE.)
e ::= ... | "{" id "=" expr "}"
– Abstract syntax (The omitted parts are the same as FAE.)
e ::= · · · | x := e
– Semantics (Some rules are omitted for brevity.)
x ∈ Domain(σ) σ(x) ∈ Domain(M )
σ, M ` n ⇒ n, M σ, M ` λx.e ⇒ hλx.e, σi, M
σ, M ` x ⇒ M (σ(x)), M

σ, M1 ` e1 ⇒ v1 , M2 a 6∈ Domain(M2 ) σ[x 7→ a], M2 [a 7→ v1 ] ` e2 ⇒ v2 , M3


σ, M1 ` val x = e1 ; e2 ⇒ v2 , M3

—End of Exam—

You might also like