λ PPL172 Assignment 2

λ
PPL172
Assignment 2
Responsible TA: Morad Muslimany
Submit your answers to the theoretical questions in a pdf file called id1 id2.pdf
and your code for programming questions inside the provided hw2.rkt, parser.rkt
and rewrite.rkt files in the correct places, and zip those files together (including
the pdf file) into a file called id1 id2.zip. Make sure that your code abides the
Design By Contract methodology. Do not send assignment-related questions by
e-mail, use the forum instead.
Theoretical questions are marked with a T, and programming questions are
marked with a P.
Have fun and may the λs always be by your side.
Deadline: 14.05.2017 at 17:00
Question 1
(18 points)
1.1 - T (3 points) What is a special form?
1.2 - T (3 points) Are all atomic expressions primitive? if your answer is ’no’,
provide an example.
1.3 - T (3 points) In what cases multiple expressions in the body of a procedure
expression (lambda form) are useful?
1.4 - T (3 points) What is a syntactic abbreviation?
1.5 - T (3 points) Recall that the let construct in Scheme is implemented as
a syntactic abbreviation. Write the syntactic form the following expression is
translated to (you do not have to compute the value of the expression, only
show the resulting translated expression):
(let ((addsquares (lambda (x y) (+ (* x x) (* y y))))
(add5 (lambda (x) (+ x 5)))
(x 120))
1
(add5 (addsquares (add5 x) (add5 240))))
1.6 - T (3 points) Consider the following conversion of or expressions to nested
if expressions:
(or arg1 arg2 ... argN) ->
(if arg1
arg1
(if arg2
arg2
...
(if argN
argN
#f)))
Recall our definition of functional equivalence. The concept as we defined it
does not include the concept of side-effects.
1.6.1 - T Are the two expressions functionally equivalent according to our
definition in class? Prove your answer.
1.6.2 - T Are the two expressions functionally equivalent when considering
side-effects as well? Prove your answer.
Recall our definition of shortcut semantics.
1.6.3 - T Do or expressions in Racket support shortcut semantics? Prove
your answer.
1.6.4 - T Does the translated if expression above support shortcut semantics? Prove your answer.
2
Question 2
(9 points)
This question refers to the language L3 as seen in the lectures.
Show the steps of evaluating the following expressions according to the evaluation rules for L3 as seen in class. At each step, describe what expression
is being evaluated, what type of evaluation is it (atomic/compound special
form/compound non-special form), and what is the resulting value. If the return
value is received from the global environment, state that fact next to the return
value. You are provided with an example and its’ solution in the next page.
2.1 - T (3 points)
(define length
(lambda (l)
(if (empty? l)
0
(+ 1 (length (cdr l))))))
2.2 - T (3 points)
(define length
(lambda (l)
(if (empty? l)
0
(+ 1 (length (cdr l))))))
(define mylist (cons 1 (cons 2 ’())))
(length mylist)
2.3 - T (3 points)
(define x 3)
(define y 0)
(+ x y y)
(lambda (x) (+ x y y))
(lambda (y) (lambda (x) (+ x y y)))
((lambda (x) (+ x y y)) 5 )
((lambda (y) (lambda (x) (+ x y y))) 2)
(((lambda (y) (lambda (x) (+ x y y))) 2) 5)
3
Example
(define x -5)
(if (> x 0)
x
(- 0 x))
Example solution
evaluate((define x 5)) [compound special form]
evaluate(5) [atomic]
return value: 5
add the binding <<x>,5> to the GE
return value: void
evaluate((if (> x 0)
x
(- 0 x))) [compound special form]
evaluate((> x 0)) [compound non-special form]
evaluate(>) [atomic]
return value: #<procedure:>>
evaluate(x) [atomic]
return value: 5 (GE)
evaluate(0) [atomic]
return value: 0
return value: #t
evaluate(x) [atomic]
return value: 5 (GE)
return value: 5
4
Question 3
(6 points)
For each of the following pieces of code, and for each binding instance (variable
declaration), state its scope and in which lines it appears bound. Furthermore,
state which variables are free. If a variable is bound only in some lambda body,
state in parenthesis the line number at which the lambda definition begins.
Example
(define even? (lambda (n)
(eq? (modulo n 2) 0)))
(even? 5)
;1
;2
;3
a possible answer can be:
Binding Instance
Appears first at line #
Scope
Line #s of bound occurrences
even?
n
1
1
Universal Scope
Lambda body (1)
3
2
Free variable occurrences:
eq?, modulo
3.1 - T (3 points)
(define fib (lambda (n)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1)) (fib (- n 2)))))))
(define y 5)
(fib (+ y y))
3.2 - T (3 points)
(define triple (lambda (x)
(lambda (y)
(lambda (z) (+ x y z)))))
(((triple 5) 6) 7)
5
;1
;2
;3
;4
;1
;2
;3
;4
;5
;6
Question 4
(24 points)
In each of the following programming questions you may not add helper procedures. Implement them inside the file hw2.rkt in the correct places.
You might find the following primitive procedures helpful: append, take, max.
4.1 - P (6 points) Implement the procedure sliding-window that receives a
list L and a numeric parameter 1 ≤ window ≤ (length L), and returns a list of
lists of length window that contain the elements of L slided. For example:
> (sliding-window ’(1 2 3 4
’((1) (2) (3) (4) (5))
> (sliding-window ’(1 2 3 4
’((1 2) (2 3) (3 4) (4 5))
> (sliding-window ’(1 2 3 4
’((1 2 3) (2 3 4) (3 4 5))
> (sliding-window ’(1 2 3 4
’((1 2 3 4 5))
5) 1)
5) 2)
5) 3)
5) 5)
The following is with no relation to part 4.1:
We can represent a (possibly empty or non-complete) binary tree in Scheme
as follows: the first element in every nesting level represents the root of the
sub-tree. A leaf is represented by an atom. A missing child is represented as
the empty list. For example:
5
/ \
#t 12
/ \
\
4
2
#f
is represented as:
’(5 (#t 4 2) (12 () #f))
4.2 - P (6 points) Implement the procedure greatest-node(tree) that receives a tree in the representation above whose nodes data are all non-negative
numbers, and computes the greatest node data value. If the tree is empty, the
return value should be −1. For example:
> (define coolTree ’(1 (7 (24 15 ()) (12 25 12)) (12 () ())))
> (greatest-node coolTree)
25
> (greatest-node 5)
5
> (greatest-node ’())
-1
6
4.3 - P (6 points) Implement the procedure count-node(tree,x) that receives
a tree in the representation above and an atomic x and computes the number
of nodes whose data is equal to x. Note that the tree might contain any atomic
data type (number, boolean, symbol, string). For example:
> (count-node ’(1 (#f () 1) hi) ’hi)
1
> (count-node ’(1 (#f () 1) hi) 1)
2
> (count-node ’() 20)
0
4.4 - P (6 points) Implement the procedure mirror-tree(tree) that receives
a tree in the representation above and computes the mirrored tree. Note that
the tree might contain any atomic data type. An example of a tree and its
mirror:
For the tree:
’(5 (#t 4 2) (12 () 3))
5
/ \
#t 12
/ \
\
4
2
3
the mirrored tree is:
’(5 (12 3 ()) (#t 2 4))
5
/ \
12 #t
/
/ \
3
2
4
7
Question 5
(10 points)
At times, it is convenient to gradually define variables. Suppose we would
like to say that x equals 5 and that y equals x · 3, then one would expect that
y equals 15, but if we write the following code, y would be equal to 3 instead:
> (define x 1)
> (let ((x 5)
(y (* x 3)))
y)
3
To achieve the result we want, we can nest let expressions as follows:
> (let ((x 5))
(let ((y (* x 3)))
y))
15
We can abbreviate the nesting of the let expressions as let*:
> (let* ((x 5)
(y (* x 3)))
y)
15
You are asked to support let* expressions as a syntactic abbreviation. You
should extend the parser code shown in class (parser.rkt) and the rewrite
methods shown in rewrite.rkt.
You must:
1. Extend the BNF of the language to support let*
2. Extend the AST datatype definitions
3. Implement the let* AST functions
4. Implement the rewrite-all-let* function with the following contract:
; Purpose: rewrite expressions containing let*-exp into ASTs that contain only let-exp
; Signature: rewrite-all-let*(exp)
; Type: [CExp -> CExp]
The function must replace all let* expressions in the input expression to
the nested-let form.
Example:
> (rewrite-all-let* (parse ’(let* ((x 5) (y (f x))) (+ x y) (- y x))))
’(let-exp
((binding (var-exp x) (num-exp 5)))
8
((let-exp
((binding (var-exp y) (app-exp (var-exp f) ((var-exp x)))))
((app-exp (var-exp +) ((var-exp x) (var-exp y)))
(app-exp (var-exp -) ((var-exp y) (var-exp x)))))))
(question 6 is on the next page)
9
Question 6
(35 points)
We have witnessed the power in the concept of syntax driven programming, in
which the main idea is to treat programs as data. We have seen both in class
and in question 5 how easy it can be to support new expression types, or change
the syntactic behavior of the program as we see fit.
In this question, we will explore the ability to ”unparse” an AST into a different
programming language. This process’ difficulty indeed depends on the lexical
and syntactic properties of both the source language and the target language.
In the parser code seen in class, there is a ready ”unparse” procedure that when
given a Scheme AST as we defined it, unparses it back to its lexical Scheme form.
So far in the course, we have seen two programming languages: Javascript and
Scheme. In this question, we will write an ”unparse” procedure that, instead
of unparsing a Scheme AST into its equivalent Scheme lexical form, unparses it
to an (almost) equivalent Javascript lexical form. Almost - because let expressions can not be translated to equivalent Javascript expressions from the point
of view of scopes. However, we will ignore that issue.
The two programming questions below should be programmed in the file hw2.rkt
in the correct places.
Part 6.1 - P (25 points)
In this part of the question, we will define the following ”unparsing” rules:
(define <var> <exp>) -> const <var> = <exp>;
<number> -> <number>
#t -> true
#f -> false
= -> ==
(<proc> <arg1> ... <argN>) -> <proc>(<arg1>,...,<argN>)
(if <p> <t> <e>) -> <p> ? <t> : <e>
(let ((var1 <exp1>) ... (varN <expN>)) <body1> ... <bodyK>)
-> let var1 = <exp1>, ..., varN = <expN>; <body1>; ...; <bodyK>;
<string> -> <string>
(lambda (param1 ... paramN) <body1> ... <bodyK>)
-> (param1,...,paramN) => { <body1>; ...; <bodyK> }
10
Note that the rule:
(proc param1 ... paramN) -> proc(param1,...,paramN)
applies to the arithmetic operators as well, meaning that in this part of the
question we will ignore the infix nature of the arithmetic operators in Javascript.
For example, in this part, we will have the following unparsed expressions:
(+ 5 x 2) -> +(5,x,2)
(+ 5 x (* 2 1 1)) -> +(5,x,*(2,1,1))
We will handle the infix notation in the next part of the question.
Implement the procedure unparse->js(ast,output-port) inside parser.rkt
that upon receiving a Scheme AST and an output-port, writes to the outputport the equivalent Javascript expression as defined by the rules above.
Use the fprintf procedure to write to the output port. You can read more
about it here: Racket Documentation. Here are a few examples of using its
syntax:
(fprintf output-port "const ")
(fprintf output-port "~a equals ~a" (get-value x) (get-value y))
(fprintf output-port " ~s " some-string)
Note that in Racket, (current-output-port) yields the currently active output port, which usually is the interpreter terminal (bottom part of DrRacket).
For clarity, you are provided with a few examples of using unparse->js:
> (unparse->js (parse ’(define x (f x (* 5 x) (= 3 2) y)))
(current-output-port))
const x = f(x,*(5,x),==(3,2),y);
> (unparse->js (parse ’(lambda (x y z)
(display "hi!")
(if (eq? x y)
z
(or #f (eq? x z)))))
(current-output-port))
(x,y,z) => { display("hi!"); eq?(x,y) ? z : or(false,eq?(x,z)) }
> (unparse->js (parse ’(lambda (x)
(let ((y (+ (f x) 5))
(z y))
(+ z y)
#t)))
(current-output-port))
(x) => { let y = +(f(x),5), z = y; +(z,y); true;
11
}
> (unparse->js (parse ’(lambda (x y z) x y z))
(current-output-port))
(x,y,z) => { x; y; z }
> (unparse->js (parse ’(f (x (g (y (z x))))))
(current-output-port))
f(x(g(y(z(x)))))
Part 6.2 - P (10 points)
Implement the procedure unparse->js-infix(ast,output-port) that behaves
precisely like unparse->js except that it handles correctly the infix nature of
the arithmetic operators +,-,*,/,==. That is, the only changed rule is as follows:
(proc param1 ... paramN) ->
if proc is one of {+,-,*,/,=} then:
(param1 proc param2 proc ... proc paramN)
otherwise:
proc(param1,...,paramN)
For example:
> (unparse->js-infix (parse ’(+ 5 3 (* 2 (- x y w) 7) z 4))
(current-output-port))
(5 + 3 + (2 * (x - y - w) * 7) + z + 4)
> (unparse->js-infix (parse ’(lambda (x y)
(if (= x y)
(+ y y)
(* (- x 0) y (/ x 1 y)))))
(current-output-port))
(x,y) => { (x == y) ? (y + y) : ((x - 0) * y * (x / 1 / y)) }
Have Fun, λ
12