Higher-Order Programming » Lab Sessions
Session 8 (Solutions)
Assistant: Steven Keuchel (steven.keuchel@vub.be)
General Remarks
As it is not easy to publish the solutions for these exercises on a single web page, I will add executable solutions to each exercise.
Therefore, a short summary of what is changed in each exercise is given here, together with a link to that file that contains ONLY the solution of your exercise, in case you want to verify your solution of a single exercise.
Section 1: Procedures
Exercise 1: Division by Zero
For this exercise, I've chosen to return either +Inf.0
(for positive infinity)
or -Inf.0
. The primitive is implemented as a procedure in the evaluator
which is then plugged in instead of the normal division operator:
The primitives available in the metacircular evaluator can be changed by modifying the list of primitive procedures. You are not required to give procedures in the metacircular evaluator the same name as in the implementation.
If you are puzzled: try to think in which layer you are working in: either the evaluator layer (the code you write in DrRacket), or the interpreted layer (the code you write in the REPL of the metacircular evaluator).
Section 2: Syntactic Sugar
Exercise 2: Implementing when
To implement a special form by desugaring we write
- a predicate that recognizes the special form, and
- a procedure that does the syntax transformation.
For when
these two ingredients are
which can then be plugged into the eval
function
Be sure that when you add something to eval
, that it is above the normal
procedure application.
Quasi-quotation
So far, every time you had to quote an expression you made use of '
. There is
another way for quoting, and that is quasi-quoting (`
).
In quasi-quoted expression you have access to insert a variable inside the quoted expression.
You can also insert lists inside lists...
If you want to insert the elements of the list, instead of inserting the
list itself, you make use of ,@
inside the quasi-quoted expression.
In this case, let's try to retrieve the information of the when
, in order to
create an equivalent program using if
and begin
.
Therefore, if you want to retrieve the condition and the body of the when...
Note that the body is a list of expressions, and should therefore be seen as a sequence... not a single expression. Evaluating a sequence is evaluating every expression in the expression list of the sequence in-order.
We can make use of quasi-quoting to make our converted program...
Since condition
is a single expression, it can be insterted using an ordinary
,
. However, we want to make use of ,@
, otherwise we would get the following
as a result.
In this program, begin
contains only one expression. Which is not a valid
expression since the result of display
is not an application. Therefore
splicing using ,@
results in the correct output.
Putting everything together the desugaring function when->if
can be replaced
by the following version that uses quasi-quotation:
Exercise 3: Implementing let
The solution of this exercise is quite similar. We first add an additional case to eval
...
Then we add the accessor procedures...
As before, we add a predicate to check whether an expression is a let
expression. Then we add the accessor procedures.
The clauses of a let
can be found by applying cadr
on the expression, the
body using cddr
(the same as in the previous exercise using when
). The
clauses is a list of lists where the first element (car
) is the variable name,
and the second element (cadr
) is the expression to evaluate.
Note! that in this implementation there is no error checking whether a clause contains too many expressions.
Then, we add the actual procedure that converts a let
, using an application of
a lambda
.
From a let
expression we first extract the clauses, and the body. Then from
the list of clauses we retrieve the variable names they want to define inside
the new scope, and the argument expression to evaluate in the current scope.
Note that clauses
is a list of clauses. Therefore we can make use of map
in
order to extract the parameters and arguments for the corresponding equivalent
program.
After everything is extracted, it can be converted.
Section 3: Special Forms
Exercise 4: Implementing when
We have already implemented when
before by converting an expression using
when
to something using only if
and begin
. This exercise repeats the
previous one, without converting the program into another form.
And the actual implementation of eval-when
...
Exercise 5: Implementing let
We start this exercise for the fourth time by modifying eval
...
Then we repurpose the accessor procedures from before...
Then we implement the procedure eval-let
.
At first sight, this seems difficult to understand, since a lot is going on at once.
As in exercise 3, the clauses, body, and names of the let are extracted.
However, instead of retrieving only the expression of a clause in the let, the
value needs to be evaluated. Therefore the value stored in values
should be a
list of values after evaluating every clause.
We pass an anonymous procedure to map
that first extracts the expression of
the clause, and then evaluates that expression in the environment given to
eval-let
.
After everything has been extracted, and the arguments have been evaluated, the body is evaluated in the extended environment.
In order to extend the environment we make use of extend-environment
. This
procedure takes 3 arguments...
- A list of variable names
- A list of values
- The base environment (the environment to extend)
This procedure is currently only being used for procedure application. However, it can be used for evaluating a let as well. We pass it the list of variable names from the let, the values that were evaluated by evaluating the expression of the let, and we pass the current environment as the environment that has to be extended.
After the new environment has been created, the body (which is a sequence of expressions) is evalauted in that new environment, the return value of the let, is the result of evaluating that expression.