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. Do the traditional lab preparation. That is,
- Start DrRacket.
- Check for update the
csc151
package. - Require the
csc151
package with(require csc151)
.
b. Review the self checks with your partner.
Exercises
Exercise 1: Nesting let
expressions
a. Write a nested let
-expression that binds a total of five names,
alpha
, beta
, gamma
, delta
, and epsilon
. With alpha
bound to
the value 7, and each subsequent value to twice the previous value.
That is, beta
should be 2*7, gamma
should be two times that
by applying (section * 2 <>)
to alpha
), and so on and so forth.
The body of the innermost let
should make a list
from those values.
Your result will look something like
(let ([___ ___])
(let ([___ ___])
(let ([___ ___])
(let ([___ ___])
(let ([___ ___])
(list alpha beta gamma delta epsilon))))))
b. Write a similar expression, this time with alpha
bound to 1/3
.
The remaining names should still be bound to subsequently doubled versions
of alpha
.
Exercise 2: Simplifying nested let
expressions
a. Write a let*
-expression equivalent to the let
-expression in the
previous exercise.
b. Since we’re repeating similar actions, it seems like building this
list of five elements is a natural candidate for using map
or map1
rather than let
. Sketch what such a command would look like and
compare and contrast the two solutions. (By “sketch”, we mean write
an outline of the code, or describe a solution in pictures or English.
You don’t need to write working Scheme code.)
Exercise 3: Naming procedures
It is likely that you came up with a solution to part a of the prior exercise that looks something like the following.
(let* ([alpha 1/3]
[beta (* 2 alpha)]
[gamma (* 2 beta)]
...)
(list alpha beta gamma delta epsilon))
...)))
What if you decided that instead of doubling each previous value, you wanted to add three to that value? You’ll have four different expressions to change, which seems inefficient (at least in terms of programmer workload).
a. Rewrite the expression to use the name fun
for what needs to be done
to each element of the list.
b. Change your code so that alpha
starts at 1 and fun
divides its
parameter by 3. What result do you expect?
c. Check your answer experimentally.
Exercise 4: Detour: Watching bindings in action
Bindings happen behind the scenes. It may, however, be useful to
see what bindings DrRacket is doing. The csc151
package includes
a set of operations for viewing what happens when you do a binding:
verbose-define
, verbose-let
, and verbose-let*
. Since you will
rarely need these procedures, you need to require them separately.
a. Add the following line to your definitions pane.
(require csc151/verbose-bindings)
b. Rewrite the examples from Exercise 1 to use verbose-let
.
c. Rewrite your code from Exercise 2 to use verbose-let
.
d. Rewrite your code from Exercise 3 to use verbose-let*
.
Exercise 5: 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.
(define years-to-seconds
(lambda (years)
(let* ([days-per-year 365.24]
[hours-per-day 24]
[minutes-per-hour 60]
[seconds-per-minute 60]
[seconds-per-year (* days-per-year hours-per-day
minutes-per-hour seconds-per-minute)])
(* years seconds-per-year))))
(define years-to-seconds
(let* ([days-per-year 365.24]
[hours-per-day 24]
[minutes-per-hour 60]
[seconds-per-minute 60]
[seconds-per-year (* days-per-year hours-per-day
minutes-per-hour seconds-per-minute)])
(lambda (years)
(* years seconds-per-year))))
a. Rename the first version years-to-seconds-a
and the second
years-to-seconds-b
.
b. In both versions, replace let*
with verbose-let*
.
c. Confirm that years-to-seconds-a
does, in fact,
recompute the values each time it is called.
d. Confirm that years-to-seconds-b
does not recompute the
values each time it is called. Again
e. Given that years-to-seconds-b
does not recompute each time, when
does it do the computation? (Consider when you see the messages.)
Exercise 6: Nested define
expressions
a. There are two examples related to nested define
expressions in the
reading, entitled sample-w/let
and sample-w/define
. Copy them into
your definitions pane and confirm that they work as described.
b. Note that you may find it useful to mouse over the various copies of
x
to see where they refer.
c. Consider the following procedure.
(define sample2
(lambda (x)
(list x
(let ([x (+ x 1)])
(list x)))))
What do you expect the output of (sample2 10)
to be?
d. Check your answer experimentally.
e. Consider the following definition.
(define sample3
(lambda (x)
(list x
(let ([x (+ x 1)]
[x (+ x 1)])
(list x)))))
What do you expect the output of (sample3 10)
to be?
f. Check your answer experimentally.
g. Consider the following definition.
(define sample4
(lambda (x)
(list x
(let* ([x (+ x 1)]
[x (+ x 1)])
(list x)))))
What do you expect the output of (sample4 10)
to be?
h. Check your answer experimentally.