On this page:
5.1 Some tests
5.2 define in Racket
5.3 extend-environment
5.4 eval-sequence and define
5.5 begin
5.6 Done?
7.2

5 Definitions

If we’ve been through the previous part, we can keep using the same file. Or we can use 2-lookup-in-environment.rkt as our starting point.

Passing an unchanging environment around is pretty cool. Maybe it would also be cool if the environment could also be extended with more stuff. Will try to.

We will use a “definition” to bind a name to a value, so that values can be referred to by their names. When we encounter a definition we will extend the environment with a new binding.

5.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?
 (extend-environment (list (cons 'd 2) (cons 'e 1))
                     (list 'a 'b 'c)
                     (list 5 4 3))
 (list (cons 'a 5) (cons 'b 4) (cons 'c 3) (cons 'd 2) (cons 'e 1)))
(check-equal?
 (evaluate
  '(begin
     (define a 2)
     (define b 3)
     (+ a b)))
 5)
(check-equal?
 (evaluate
  '(begin
     (define a 2)
     (define a 3)
     (+ a a)))
 6)

5.2 define in Racket

Our defintions are going to work rather like the regular Racket ones. Try out the following lines of code in the REPL:

(define a 2)

a

(define b +)

(b a 3)

(begin
  (define a 3)
  (+ a 3))

5.3 extend-environment

We would like a helper-function for extending an environment with new bindings. We want to use this for definitions, and also for adding arguments to the environment when functions are called. Functions can have multiple parameters, so we will make extend-environment take a list of names and a list of values, in addition to the environment it should extend:

(define (extend-environment env names values)
  your code here)

The function should return a new list containing new pairs of names and values and all the pairs from env. The Racket function append will be useful. Also useful: Racket’s map-function can take multiple lists of same length as arguments. Like:

(map cons (list 'a 'b 'c) (list 1 2 3))

When done, the test that uses extend-environment should pass.

5.4 eval-sequence and define

Definitions are not “expressions” in our language: We do not evaluate a definition in order to get some value. It only extends the environment. That kind of means that evaluating a program that is only a definition is not an incredibly meaningful thing to do. Like, we, uh, we want results.
So, we’re going to add support for evaluating multiple terms, on after another. Typically one or more definitions and then an expression at the end.

We make function:

(define (eval-sequence env terms)
  (match terms
    [(list exp)  your code here]
 
    [(list (list 'define name exp) rest ...)  your code here]
 
    [(list trm rest ...)  your code here]))

In the first match-clause: The list only consist of only one element. The one element in a one-element list is its final element. The final term in a sequence is the expression we want to evaluate in order to get its result value. We can use eval-exp.

In the last match-clause: Since the middle clause did not match, trm is not a define-form. We will evaluate it with eval-exp, throw away its result, and use eval-sequence on the rest of the terms. (Yea so throwing away the results seems possibly wasteful. Maybe side effects though?)

In the middle match-clause: A define-form. This is more trickier. First we should evaluate the expression part of the defintion, and create a new environment with extend-environment. Then we can call eval-sequence on the rest of the list and the new environment.

5.5 begin

We use begin-forms to create expressions out of lists of terms. We add a new match-clause in eval-exp:

[(list 'begin terms ...) your code here]

And we will use eval-sequence to deal with the terms.

5.6 Done?

Run and see that all the tests pass.
Next is Functions. We can keep using the Racket-file we’re working with, or skip to 3-definitions.rkt.