We won't be talking about this material in class, but it will help you develop your skills at reading Racket by providing a model for what functional programming is about. This chunk of lecture is adapted from course materials by Dr. Phillip J. Windley at Brigham Young University, with a few modifications.
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 on, we will expand it.
Recall that in the lecture on evaluating expressions earlier, we determinied that we could evaluate any complex expression, (operator operand1 operand2 operand3 ....), by doing the following:
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.
The model we will discuss is called the 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. To apply a lambda expression (the only kind of function we will worry about):
Perhaps the best way to explain the substitution model is with an example. Suppose we are evaluating:
And that any-old-func is defined as:
(define any-old-func (lambda (w) (sum-of-squares (- w 3) w)))
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 result to all the rest by substituting the body in place of the application and then substituting the argument 5 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 subexpression for the subexpression, giving:
(sum-of-squares 2 5)
Again, we can replace this expression with the body of the definition for sum-of-squares:
( (lambda (x y) (+ (square x) (square y))) 2 5)
And then replacing the entire application with the body and substituting the arguments 2 and 5 for the formal parameters x and y, respectively:
(+ (square 2) (square 5))
We continue on in this way 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 we have taken some short cuts; there is nothing wrong with this as long as the derivation is clear and you don't confuse yourself. Also, at any step along the way, I could have stopped and entered the current expression into Racket and it would evaluate.
There is nothing special about the order in which we substitute for subexpressions. For example, we could have continued the above example like this:
(+ (square 2) (* 5 5)) (+ (square 2) 25) (+ (* 2 2) 25) (+ 4 25) 29
In fact, we could even do it like this:
(+ (* 2 2) (* 5 5)) (+ 4 25) 29
Let's try to use the substitution model to work through an application of the compose function of we saw earlier.
((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 apply it to analyzing your programs in Racket when your are stuck, you will find that it is a powerful tool for programming.
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) )