...
- (40 pts.) Write an evaluator for arithmetic expressions as follows:
- Write the (function) template for
ArithExpr
Write a function
to-list
that maps anArithExpr
to the corresponding "list" representation in Racket. Numbers are unchanged. Some other examples include:Code Block (to-list 21) => 21 (to-list (make-sum (make-prod 4 7) 25)) => '(+ (* 4 7) 25) (to-list (make-quot (make-diff 4 7) 25)) => '(/ (- 4 7) 25)
Notes:
The notation
'(+ (* 4 7) 25)
abbreviates(list '+ (list '* 4 7) 25)
.You need to define the output type (named
RacketExpr
) for this function, but you can omit the template because this assignment does not include any functions that process this type. There are several mathematically distinct definitions that are correct. Some are more restrictive (narrower) than others but all are correct. In general, narrow definitions are better because they imply stronger claims about the elements of the type.The notation
'(+ (* 4 7) 25)
abbreviates(list '+ (list '* 4 7) 25)
.
- Write a function
eval: ArithExpr -> number
that evaluates anArithExpr
. Your evaluator should produce exactly the same result for anArithExpr
E
that Racket evaluation would produce for the list representation(to-list E)
.
- Write the (function) template for
- (40 pts.) Extend the definition of
ArithExpr
as follows:- Add a clause for variables represented as Racket symbols.
- Write the (function) template for this extended definition; it should be similar to your template for
ArithExpr
from Problem 1. - Modify your definition of
to-list
to support the expanded definition ofArithExpr
. Given the Racket structure definition:
Code Block (define-structure binding (var val))
a
binding
is(make-binding s n)
wheres
is a symbol andn
is a number. Anenvironment
is a(list-of binding)
. Write a (function) template instantiation for processing anenvironment
.- Define a top-level variable
empty-env
that is bound to the emptyenvironment
containing no bindings (i.e., theempty
list). Note thatempty-env
is really a constant since variables cannot be re-bound in our functional subset of Racket. Write a function
extend
that takes environmentenv
, a symbols
, and a numbern
, and returns an extended environment identical toenv
except that it adds the additional binding ofs
ton
(as the most local [inner] binding).
The definition ofextend
is trivial; it requires no recursion. As a result,extend
satisfies the invariantCode Block (check-expect (extend empty-env s n) (list (make-binding s n)))
where
s
is any symbol andn
is any number. Hence,Code Block (extend empty-env 'a 4) => (list (make-binding 'a 4))
In the remainder of the problem, use
empty-env
andextend
to define example environments for test cases.- Write a function
lookup
that takes a symbols
and an environmentenv
and returns the firstbinding
inenv
with avar
component that equalss
. If no match is found,lookup
returnsempty
. Note that the return type oflookup
is not simplybinding
because it can returnempty
. Define the new union type calledoption-binding
for the return type. You do not need to write a template for this type since it is a trivial union type. Write a new
eval
function for the expanded definition ofArithExpr
. The neweval
takes two arguments: anArithExpr
E
to evaluate and anenvironment env
specifying the values of free variables inE
. For example,Code Block (eval 'x (extend empty-env 'x 17)) => 17 (eval (make-prod 4 7) (extend empty-env 'x 17)) = 28 (eval 'y (extend empty-env 'x 17)) => some form of run-time error
If an
ArithExpr
E
contains a free variable that is not bound in theenvironment env
, then(eval E env)
will naturally produce some form of run-time error if you have correctly codedeval
. Do not explicitly test for this form of error since the behavioral contract foreval
says nothing about detecting errors.
- (20 pts.) An
environment
is really a finite function (a finite set of ordered pairs). It is finite in the sense that it can be completely defined by a finite table, which is not true of nearly all the primitive and library functions in Racket (and other programming languages). Even the identity function is not finite. For the purpose of this exercise, we redefine the typeenvironment
as(symbol -> option-binding)
which includes infinite objects (sincesymbol
is infinite).- Rewrite
eval
to useenvironment
defined as the type(symbol -> option-binding)
instead of as a(list-of option-binding)
. If you cleanly coded your definition ofeval
in the preceding problem usinglookup
,make-binding
, andextend
, all that you have to do to your solution to the previous problem is redefine the bindings oflookup
,empty-env
, andextend
, and revise your test cases forextend
. You can literally copy the entire text of your solution to problem 2; change the definitions oflookup
,empty-env
, andextend
; update your documentation (annotations) concerning theenvironment
type; and revise your tests forextend
. Note thatextend
cannot be directly tested (since the result is a function!) but we can write indirect tests by usinglookup
to examine it. (Note: if you wrote a correct solution to problem 2, you can do this problem in less than 15 minutes!)
Hint: you can uselambda
-notation in Racket to define a constant function forempty-env
, andextend
can be defined as a functional that takes a function (representing an environment) and adds a new pair to the function--using anif
embedded inside alambda
-abstraction.
- Rewrite
- Extra Credit (50 pts.) Add support for
lambda
-abstractions in your evaluator from Problem 2 as follows:- Extend the definition of
ArithExpr
by adding a clause for unarylambda
-abstractions and a clause for unary applications of anArithExpr
(which can be a function!) to anArithExpr
. Use the namelam
for the structure representing alambda
-abstraction and the namesvar
andbody
for the accessors of this structure. Use the nameapp
for the structure representing a unary application and the namesrator
andrand
for the argument of this structure. Note that therator
of anapp
is anArithExpr
not alam
(which is a proper subtype ofArithExpr
). - Write a template for this additional expansion of the definition of
ArithExpr
. - Extend the definition of
to-list
to support this expansion of the definition ofArithExpr
. You print the corresponding "concrete" syntax that would be given as input to a compiler. Hence,
(to-list (make-lam 'x (make-plus 'x 'y))) => (list 'lambda (list 'x) '(+ x y)) = '(lambda (x) (+ x y))
- Extend the definition of
eval
to support this expansion ofArithExpr
. Note thateval
can now return functions as well as numbers. Your biggest challenge is determining a good representation for function values. What doeseval
return as the value of alam
input? That input may contain free variables. In principle, you could represent the value of thelam
input by a revisedlam
(with no free variables) obtained by substituting the values for free variables from the environment input (just like we do in hand-evaluation). But this approach is tedious and computationally expensive. A better strategy is to define a structure type (called a closure) to represent a function value. The structure type must contain the originallam
and a description of what substitution (of values for identifiersfree variables) would have been made, deferring the actual substitution just aseval
defers substitutions by maintaining an environment. For this problem, you can use brute force to describe the substitution, which makes constructing and applying closures nearly trivial to code. In practice, there are much more computationally efficient approaches.
- Extend the definition of