Racket's Substitution Model
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:
- Evaluate the operator. If it is a function...
- Evaluate each of the remaining sub-expressions.
- 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:
-
Substitute the body of the
lambda
expression for the application (that is, the entire expression including the function and its parameters) - 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:
- substituting the body of the function in place of the application, and
-
substituting the arguments
2
and5
for the formal parametersx
andy
, 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.
This is quite a bit more involved, but hopefully you see what a convenient tool substitution is for analyzing expressions.
A few comments:
- Notice that I ordered the substitution so that the expressions didn't become unwieldy.
- The order of substitution and expansion does not make any difference to the outcome for any pure function. No sub-expression affects the value of any other sub-expression.
- Substitution is best done using an editor where one can easily cut and paste!
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) )