Racket's Substitution Model

I adapted the material on this page from open course material by Dr. Phillip J. Windley at Brigham Young University, originally published in the 1990s. I have made several modifications.

The Substitution Model of Function Application

We learned the idea underlying this material in class when we learned how to evaluate a compound expression, and we have used it repeatedly every session since. Even so, this reading will help you develop your skills at reading Racket by providing a model for what functional programming is about.

In order to understand function evaluation, we need a model. For a language such as Java, the evaluation model can be quite complex. However, because of Racket's consistent syntax, and the way it uses functions, our model of evaluation is quite simple. Later in the course, we will expand it.

In Session 2, we saw that we could evaluate any compound expression, (operator operand1 operand2 operand3 ....), by doing the following:

  1. Evaluate the operator. If it is a function...
  2. Evaluate each of the remaining sub-expressions.
  3. Apply the function to the sub-expressions.

This evaluation model is valuable, but not detailed enough. In particular, we haven't discussed how we evaluate names and we don't know what it means to apply something.

We call our evaluation model a substitution model, because when we evaluate a name, we substitute its definition in place of the name. Applying a function to its parameters is also done using substitution. Here's how.

To apply a lambda expression:

  1. Substitute the body of the lambda expression for the application (that is, the entire expression including the function and its parameters)
  2. Substitute the parameters into the body on a one-for-one basis with the arguments.

Perhaps the best way to explain the substitution model is with an example. Suppose we are evaluating:

(any-old-func 5)

... and that any-old-func is defined as:

(define any-old-func
  (lambda (w)
    (sum-of-squares (- w 3) w)))

(We last saw sum-of-squares in Session 5.)

We can find the result of evaluating the application (any-old-func 5) by following our algorithm. First, we substitute the definition of any-old-func for the name:

( (lambda (w) (sum-of-squares (- w 3) w)) 5)

Next, we apply the leftmost item to values of the arguments by substituting the body of the function in place of the application with the argument 5 substitute for the formal parameter w in the body:

(sum-of-squares (- 5 3) 5)

This expression can be simplified by substituting the value of the second sub-expression for the sub-expression, giving:

(sum-of-squares 2 5)

To evaluate this expression, we use the same process again: We replace sum-of-squares with its value, which is a lambda expression:

( (lambda (x y) (+ (square x) (square y))) 2 5)

Next, we apply the leftmost item to values of the arguments 2 and 5 by:

  1. substituting the body of the function in place of the application, and
  2. substituting the arguments 2 and 5 for the formal parameters x and y, respectively:
(+ (square 2) (square 5))

We continue on in this way, applying the same algorithm recursively until we arrive at an answer:

(+ (* 2 2) (square 5))
    
(+ 4 (square 5))

(+ 4 (* 5 5))

(+ 4 25)

29

Note that in these last steps I have taken some short cuts; there is nothing wrong with this as long as the derivation is clear and you don't confuse yourself.

There is nothing special about the order in which we substitute for the sub-expressions. For example, we could have continued the above example like this:

(+ (square 2) (* 5 5))

(+ (square 2) 25)

(+ (* 2 2) 25)

(+ 4 25)

29

Now consider the function compose, which takes two functions as arguments and returns a function as its value:

(define (compose f g)
  (lambda (x)
    (f (g x))))

Try to use the substitution model we've just learned to evaluate this application of compose. After tracing the application until you have an answer, click on the to reveal one possible trace.

((compose add2 add2) 3)
(((lambda (f g) (lambda (x) (f (g x)))) add2 add2) 3)

((lambda (x) (add2 (add2 x))) 3)

(add2 (add2 3))

(add2 ((lambda (n) (+ n 2)) 3))

(add2 (+ 3 2))

(add2 5)

((lambda (n) (+ n 2)) 5)

(+ 5 2)

7

This is quite a bit more involved, but hopefully you see what a convenient tool substitution is for analyzing expressions.

A few comments:

If you learn the substitution model and use it to analyze your code whenever your get stuck, you will find that it is a powerful tool for programming.

Exercises

Evaluate each of the following using the substitution model:

(square (any-old-func 6))

(sum-of-squares (square 6) (any-old-func 2))

(any-old-func (sum-of-squares (+ 4 5) (+ 2 3)))

(square (sum-of-squares (cube (+ (- 5 3) 2))
                        (sum-of-squares (+ 2 1)
                                        (* 3 2))))

( (compose car cdr) '(1 2 3 4) )