Laboratory: Naming Values with Local Bindings
Summary:
In this laboratory, you will ground your understanding of the basic
techniques for locally naming values and procedures in Scheme,
let and let*.
Preparation
a. In the definitions pane, create a new 20x20 image and name it
canvas:
(define canvas (image-show (image-new 20 20)))
b. Click Run.
c. Zoom in to at least 800%.
Exercises
Exercise 1: Evaluating let
What are the values of the following let-expressions?
You may use MediaScheme to help you answer these questions, but be sure you
can explain how it arrived at its answers.
a.
(let ((tone "fa")
(call-me "al"))
(string-append call-me tone "l" tone))
b.
(let ((total (+ 8 3 4 2 7)))
(let ((mean (/ total 5)))
(* mean mean)))
c.
(let ((inches-per-foot 12)
(feet-per-mile 5280.0))
(let ((inches-per-mile (* inches-per-foot feet-per-mile)))
(* inches-per-mile inches-per-mile)))
Exercise 2: Nesting Lets
a. Write a nested
let-expression that binds a total of
six names, row, alpha,
beta, gamma, delta,
and epsilon, with row bound to 0,
alpha bound to
RGB-BLUE, and each subsequent value to a redder
version of of the previous name.
That is,
beta should be a a redder version of alpha,
gamma a redder version of beta, and
so on and so forth. The body of the innermost let
should set five pixels of canvas to those colors.
Your result will look something like
(let ((...))
(let ((...))
(let ((...))
(let ((...))
(let ((...))
(let ((...))
(image-set-pixel! canvas 0 row alpha)
(image-set-pixel! canvas 1 row beta)
(image-set-pixel! canvas 2 row gamma)
(image-set-pixel! canvas 3 row delta)
(image-set-pixel! canvas 4 row epsilon)))))))
b. Write a similar expression, this time with row
bound to the value 1 and alpha bound to
RGB-BLACK. The remaining names should still be bound
to subsequently redder versions of alpha.
Exercise 3: Simplifying Nested Lets
a. Write a let*-expression equivalent to the
let-expression in the previous exercise, but using a
different starting color and row.
b. Since we're repeating similar actions, it seems like setting
five pixels to five shades of a color is a natural candidate for
using map or for-each.
Sketch what such a command would look like (don't use
let), and compare and contrast the two solutions.
When you are done, you may want to read the notes on this problem.
Exercise 4: Global vs. Local
Consider the following expression, which is a potential
solution to the previous problem.
(let* ((row 3)
(alpha RGB-GRAY)
(beta (rgb-redder alpha))
(gamma (rgb-redder beta))
(delta (rgb-redder gamma))
(epsilon (rgb-redder delta)))
(image-set-pixel! canvas 0 row alpha)
(image-set-pixel! canvas 1 row beta)
(image-set-pixel! canvas 2 row gamma)
(image-set-pixel! canvas 3 row delta)
(image-set-pixel! canvas 4 row epsilon))
a. What do you expect to have happen if we add the following
definition before evalating that expression?
(define RGB-GRAY (rgb-new 0 255 0))
b. Check your answer experimentally.
c. Click Run to restore the value of gray.
d. What do you expect to have happen if we add the following definition
before evaluating the let* expression?
(define rgb-redder
(lambda (rgb)
(rgb-new (- (rgb-red rgb) 32)
(- (rgb-green rgb) 32)
(+ (rgb-blue rgb) 32))))
e. Check your answer experimentally.
f. Comment out or delete your definition of rgb-redder
and click Run to restore the value of
rgb-redder.
g. Suggest a way that we might avoid the kind of problem you just
encountered.
h. What do you expect to have happen if we add the following definition
before evaluating the let* expression?
(define + -)
i. Check your answer experimentally.
j. Comment out or delete your definition of + and click
Run to restore the value of
+.
Exercise 5: Detour: Printing Values
Sometimes it's useful to see values as they are being computed.
Here's a procedure that makes it easy to tell when an expression is being
used. It prints the value it is called with and then returns the value.
a. What do you expect to happen when you execute the following
command?
(+ (value 5) (value 7))
b. Check your answer experimentally.
c. What do you expect to happen when you execute the following command?
(* (value (+ (value 2) (value 3))) (value (+ (value 1) (value 1))))
d. Check your answer experimentally.
e. What do you expect to happen when you execute the following
command?
(define tmp (value (* 3 4 5)))
Exercise 6: Ordering Bindings
In the reading, we
noted that it is possible to move bindings outside of the lambda in
a procedure definition. In particular, we noted that the first of
the two following versions of years-to-seconds required
recomputation of seconds-per-year every time it was called
while the second required that computation only once.
a. Rename the first version years-to-seconds-a and
the second years-to-seconds-b.
b. Using value, confirm that years-to-seconds-a does, in fact,
recompute the values each time it is called. You might, for example, replace
(seconds-per-year (* days-per-year hours-per-day
minutes-per-hour seconds-per-minute)))
with
(seconds-per-year (value (* days-per-year hours-per-day
minutes-per-hour seconds-per-minute))))
c. Confirm that years-to-seconds-b does not recompute the
values each time it is called. Again, make changes like those reported above.
d. Given that years-to-seconds-b does not recompute each
time, when does it do the computation? (Consider when you see the
messages.)
Exercise 7: Ordering Bindings, Revisited
You may recall that we defined a procedure to compute a grey with the
same brightness as a given color.
You might be tempted to move the let clause outside the
lambda, just as we did in the previous exercise. However, as we noted
in this reading, this reordering will fail.
a. Verify that it will not work to move the let before
the lambda, as in
(define rgb-greyscale
(let ((brightness (+ (* 0.30 (rgb-red color))
(* 0.59 (rgb-green color))
(* 0.11 (rgb-blue color)))))
(lambda (color)
(rgb-new brightness brightness brightness))))
b. Explain, in your own words, why this fails (and why it should fail).
Notes on the Exercises
Notes on Exercise 3
a. Here's the form of answer we expected.
(let* ((row 3)
(alpha RGB-GRAY)
(beta (rgb-redder alpha))
(gamma (rgb-redder beta))
(delta (rgb-redder gamma))
(epsilon (rgb-redder delta)))
(image-set-pixel! canvas 0 row alpha)
(image-set-pixel! canvas 1 row beta)
(image-set-pixel! canvas 2 row gamma)
(image-set-pixel! canvas 3 row delta)
(image-set-pixel! canvas 4 row epsilon))
b. The problem is relatively simple if we just want to set each pixel
to the same color. To set row 6, we can use
(for-each (lambda (col) (image-set-pixel! canvas col 6 RGB-GRAY))
(iota 5))
But how do we deal with the issue that we want each pixel a different
color? We could start by making the list of colors one of the
parameters to the for-each.
(for-each (lambda (col rgb) (image-set-pixel! canvas col 6 rgb))
(iota 5)
(list alpha beta gamma delta epsilon))
Now we need to figure out how to get those five colors. We
could write the expressions explicitly.
(for-each (lambda (col rgb) (image-set-pixel! canvas col 6 rgb))
(iota 5)
(list
RGB-GRAY
(rgb-redder RGB-GRAY)
(rgb-redder (rgb-redder RGB-GRAY))
(rgb-redder (rgb-redder (rgb-redder RGB-GRAY)))
(rgb-redder (rgb-redder (rgb-redder (rgb-redder RGB-GRAY))))))
Hmmm ... that's almost as long as the original. It's also slightly
less readable. Worst of all, it's much less efficient: ten calls
to rgb-redder rather than four.
So, what should we do? We can stick with the original let
expression. We can build a hybrid expression (one in which we generate
the colors using a let* and then show them with
for-each).
(let* ((row 6)
(alpha RGB-GRAY)
(beta (rgb-redder alpha))
(gamma (rgb-redder beta))
(delta (rgb-redder gamma))
(epsilon (rgb-redder delta)))
(for-each (lambda (col rgb) (image-set-pixel! canvas col row rgb))
(iota 5)
(list alpha beta gamma delta epsilon)))
Perhaps we can find a more creative way to make the sequence of
colored pixels. We'll leave that last technique to your imagination.