4 Lookup in the environment
If we’ve been through the previous part, we can keep using the same file. Or we can use 1-fixed-calculator.rkt as our starting point.
We see that most of the match-clauses in eval-exp look quite similar. We will do some steps of refactoring in order to not repeat ourselves, and also to be prepared for things to come. Later the users of the program will be able to define their own variables with names they choose themselves, so we do not want to continue to match on the names ('+, '- and so on) as we currently do in eval-exp. Instead we will look up the values of variables in some lookup-table that can be extended dynamically.
4.1 Some tests
You might want to copy them into the test module at the bottom of your Racket file. The tests are going to fail at first. By the end of this chapter they should pass.
(check-equal? (lookup (list (cons 'a 1) (cons 'b 2)) 'a) 1)
(check-equal? (lookup (list (cons 'a 1) (cons 'b 2)) 'b) 2)
(check-equal? (lookup (list (cons 'a 0) (cons 'a 1) (cons 'b 2)) 'a) 0)
(check-exn exn:fail? (λ () (lookup (list (cons 'a 1) (cons 'b 2)) 'c)))
4.2 An environment
Idea is: Whenever we evaluate an expression, we will have an environment with bound variables and their values, and when we evaluate a variable reference, we will look up its value in the environment.
An environment can look like:
'((a . 5) (b . 4) (c . 3) (a . 7))
In this environment, looking up 'b should give us 4 and 'c should give us 3. 'a should give us 5. New bindings are added to the beginning of the list, and newer bindings shadow older ones. We can’t reach the 'a that is bound to 7.
4.3 primitives
(define primitives (list (cons '+ +) more bindings))
4.4 lookup
We will implement this as a recursive function. If the first binding in the list is equal to the symbol we are looking for then we return the corresponding value, otherwise we call lookup on what remains of the list. If the list is empty it doesn’t have the binding we’re looking for, and we will raise an exception.
(define (lookup env s) (match env your code here))
It should have a couple of match-clauses:
[(list (cons name val) rest ...) your code here]
The tests we added should pass when we’re done.
4.5 Environment as input to eval-exp
We will pass primitives as an argument to eval-exp, and make eval-exp use it to look up values. So, in the definition of eval-exp, instead of
(eval-exp exp)
we will have have
(eval-exp env exp)
env will be the list of bindings we will use for looking up stuff.
We should modify the evaluate-function so that it passes primitives along to eval-exp.
4.5.1 Looking up
[(? symbol?) your code here]
When this matches we want to apply our lookup-function to env and exp.
4.5.2 eval-application
Now, instead of having different match-clauses for '+ and '- and so on, we can have one clause for function application:
[(list fun args ...) (eval-application env fun args)]
We should be able to evaluate the fun-expression to get the correct function. We’re using a helper function:
(define (eval-application env fun args) your code here)
It should evaluate the fun-expression and all the expressions in the args list, and apply the evaluated fun to the evaluated args.
Should be pretty similar to what we used to do when we matched e.g. '+, only we don’t use a hardcoded function (like +), and we need to pass the environment along whenever we call eval-exp. It can be useful with a lambda function.