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. Now that
we have begun to work with
lambda expressions,
this reading can take you deeper and 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
lambdaexpression for the application, that is, the entire expression including the function and its arguments. - 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 that we have a function
any-old-func defined as:
(define any-old-func
(lambda (w)
(sum-of-squares (- w 3) w)))
... and that we are evaluating:
(any-old-func 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 operator to the 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
2and5for the formal parametersxandy, 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 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 too 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) )