Implement a new kind of binary fold called fold-pairs which works by
combining every two consecutive elements (and if there’s a single
leftover, leave it as is), then repeat this until we get a list with just
one element which is the final result. Note that this requires having a
non-empty list as the initial input, which means that there’s no need to
get an "initial value" input.
Do this in two steps. In the first step, define a map-pairs function
that does the pair scan and produces a list of results. Here are a few
tests to demonstrate how it works:
Note that a proper type for this will be tricky, so you can skip it, and
instead describe things more accurately in the purpose statement.
Question 3: BNF Grammar
15 pts
In class, we have seen a BNF for an infix syntax of the four basic
arithmetical expressions, which properly makes multiplication and division
have higher precedence, each level of operators is left-associative and we
also had parenthesis grouping:
(The spaces in the above are just for readability, since we don’t
specify them in the syntax.)
Question 4: Fill in the blanks
20 pts
For each one of the following expressions, write a definition for blank
in Racket that makes the expression evaluate to 660. For example, a
correct answer for this expression:
(* (blank) 2)
is:
(define blank (lambda () 330))
If you think that it is impossible to write such a definition, explain why
this is the case. (Remember: this is Racket, not Schlac or FLANG, or
any other language that we’ve seen.)
The language is always the #lang pl that you’ve used so far. Unless
explicitly specified, you do not need to write types for the definitions, and
assume no mutation is allowed.
Do this for the following expressions.
(* blank (+(*)(*)))
(/ blank 12345)
Hint: you do not need a calculator for this
((lambda () (blank)))
(Y blank)
This is assuming our definition of Y for Racket that we’ve seen in
class:
(let* ([blink blank] [blank blink]) blank)
Reminder: let* works as a shorthand for a bunch of nested
single-identifier binding let forms
Question 5: Structural Recursion with ASTs
18 pts
Implement a shadowed-identifiers function that consumes an AST syntax
for FLANG (an input of type FLANG), and returns a list of all identifiers
(a list of symbols) that are "shadowed" anywhere in the code. An
identifier is shadowed if in its scope there is some arbitrarily nested
scope with the same name. A few examples:
Note: you don’t need to worry about the order of symbols and repeating
ones. This would be addressed using a predicate that compares two lists as
sets, which you don’t need for this question.
Question 6: Extending the FLANG Language
18 pts
We want to extend the FLANG langugae with a new feature called upref.
This will be a new special form that has an identifier, and it will
evaluate to the value of the second binding of that identifer in the
runtime environment. Again, we use a few tests to demonstrate this
extension:
The main step is extending eval to handle these expressions:
(: eval : FLANG ENV -> VAL)
;; evaluates FLANG expressions by reducing them to values
(define (eval expr env)
(cases expr
...same...
[(Upref name) ???]))
Your job is to do so. You don’t need to copy any code, it’s enough to
specify just the expression that should be written in the place marked
with "???". It is best to do this using a new helper function — and
you need to write the code for this function as well (including its type
and a clear purpose statement).
Question 7: Schlac
16 pts
The standard way to encode booleans that we have seen in class is as two
selector functions:
As usual, that’s not the only way you could encode them. We could, for
example, try to do a more C-style encoding and use 0 for false, and
any number bigger than 0 for true:
(define #f 0) ; note: it's actually the same as what we had!
(define #t 1) ; or 2, or 3, etc
This change will obviously require changing other aspects of booleans,
and the most basic function that we need to deal with is if. Write
its new definition:
(define if
(lambda (cond then else)
...fill-in...))
Question 8: Language Features
18 pts
Unless told otherwise, choose the best answer in each of the
following questions. (Note that there could be several answers that are
correct, but one should be the most accurate.)
In the FLANG extension that we have implemented above we added a new kind
of an "upref" identifier reference. An important issue is whether our
language is still one that can be compiled or not. Can it?
No, the fact that we can do this kind of upref dynamically at
runtime means that we get a dynamically scoped language, and as we have
discussed in class, such langugaes cannot be compiled. (Since the
"offset" of a variable reference cannot be determined before running the
code.)
Almost, but no. Since all of the references are visible in the
source code, we can still tell which binding is referenced at each
upvar expression — but this works only if our language is not
extended to have a conditional expression like if. Such an
expression means that we can have code that goes an arbitrary number of
levels at runtime, so we cannot tell statically which binding it refers
to.
Yes. Since the syntax still allows us to see the lexical structure
of the code, we can still analyze it and compile it using de-Bruijn
indexes. Even if we have a conditional if form added to the language
we can see the lexical structure of the code since an upref
expression returns a value, which means that we cannot do two (or more)
uprefs somehow.
Yes. Since the syntax still allows us to see the lexical structure
of the code, we can still analyze it and compile it using de-Bruijn
indexes. If we have a conditional ‘if’ form added to the language we
will need to use a lazy evaluation strategy anyway, and that means
that we don’t get expressions with a dynamically determined scope.
In the last midterm exam we’ve had this "blank" question:
(* 330 (length (cons blank blank)))
and a solution that was given to it was:
(define blank (list 1))
In the review session, I mentioned the fact that typing this might be
tricky. To see this, you need to be aware of the type of cons:
(: cons : (All (A) (A (Listof A) -> (Listof A))))
Which of the following types can be used for this blank definition to
work?
Any
(Listof Any)
(Listof Number)
(All (A) (Listof A))
(List Number)
Number
There are exactly three of these that can work, and three that will not.
You need to list all three.
Joe implemented a Flang evaluator similar to ours in an imperative
language, whose name begins with "J". (This question is based on a true
story from a past semester. The names have been changed to protect the
innocent.)
As common for such programmers, he made the mistake of making environment
extension modify via mutation of the environment. Write a simple
Flang program that demonstrates that his implementation is wrong.
Note: you need to find such an example yourself, no multiple choices here.
(But it is easy to do this.)
We’ve seen in class that if we write (list (foo)) in Racket, then the
foo call is not a tail call. Intuitively, this is because there is
still work to be done with the return value (wrap it in a list). What if
we do something like this:
((lambda (x) x) (foo))
— would the foo call be a tail call now?
Yes, it would be a tail call. This is because dynamic scope means
that there are no closures, so the lambda expression basically
evaluates to just the code with no environment to hold on to the value
of x. This means that when it is applied, we get just the value of
the foo call, and there is no need to do something with that when
we get an answer.
No, it still would not be a tail call. This is because Racket is
a strict language, and therefore no matter which function is called, we
still need to evaluate the argument and only then apply the anonymous
function onto the resulting value.
Yes, it would be a tail-call now. Racket’s compiler performs
optimizations like reducing an immediate ((lambda (x) ...) ...) to
avoid runtime work on generating a closure that is immediately applied,
and therefore the code that the whole expression compiles to is
essentially just (foo), and that obviously has the semantics of a
tail call (since there is no context).
No, it still would not not be a tail call. This is because we
have a language with first-class functions, and the only way to
optimize away such calls of unknown functions is by a complete lexical
analysis of calls, one that can be performed only on a more restricted
language, one with just first-order functions.