| Schedule | Readings | Labs | Homework | Mechanics | Contact | |
| CSC 151-01, 2007S » Lab 12 » Local Bindings | ||||||
Summary: In this laboratory, you
will
ground your
understanding of the basic techniques for naming values and procedures
in Scheme, let and let*.
Contents:
let letWhat are the values of the following let-expressions?
You may use DrScheme 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"))
(list 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))
(let ((inches-per-mile (* inches-per-foot feet-per-mile)))
(* inches-per-mile inches-per-mile)))
Write a nested let-expression that
binds a total of five
names, alpha, beta,
gamma,
delta, and epsilon,
with alpha
bound to 9387 and each subsequent name bound to a value twice
as large as the one before it. That is, beta
should be
twice as large as alpha, gamma
twice as
large as beta, and so on. The body of the
innermost
let-expression should then compute the sum of
the values
of the five names.
Write a let*-expression equivalent
to the
let-expression in the previous exercise.
The file "/home/davisjan/csc/151/examples/verbose-bindings.ss"
contains alternative versions of define, let,
and let*. These alternative versions are named verbose-define,
verbose-let, and verbose-let*. They provide information about bindings that may be helpful for understanding the behavior of your code.
a. Load this file.
b. Rewrite the examples from Exercise 1 to use verbose-let.
c. Rewrite your code from Exercise 2 to use vebose-let.
d. Rewrite your code from Exercise 3 to use verbose-let*.
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-a
(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-b
(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. Confirm that years-to-seconds-a
does, in fact, recompute
the values each time it is called. (You might, for exaple, replace
let* by verbose-let*.)
b. Confirm that years-to-seconds-b
does not recompute the
values each time it is called. (Again, replace the let*
by verbose-let*.)
c. Given that years-to-seconds-b does not
recompute each
time, when does it do the computation? (Consider when you see the
messages.)
You may recall that we defined a procedure to compute the roots of a quadratic polynomial as follows:
(define roots
(lambda (a b c)
(let ((negative-b (- b))
(sqrt-discriminant (sqrt (- (* b b) (* 4 a c))))
(two-a (* 2 a)))
(list (/ (+ negative-b sqrt-discriminant) two-a)
(/ (- negative-b sqrt-discriminant) two-a)))))
You might be tempted to move the let
clause outside the
lambda, just as we did in the previous exericse. 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 roots
(let ((negative-b (- b))
(sqrt-discriminant (sqrt (- (* b b) (* 4 a c))))
(two-a (* 2 a)))
(lambda (a b c)
(list (/ (+ negative-b sqrt-discriminant) two-a)
(/ (- negative-b sqrt-discriminant) two-a)))))
b. Explain, in your own words, why this fails (and why it should fail).
Consider the following procedure that squares all the values in a list.
;;; Procedure:
;;; square-values
;;; Parameters:
;;; lst, a list of numbers of the form (num_1 num_2 ... num_n)
;;; Purpose:
;;; Squares all the values in lst.
;;; Produces:
;;; list-of-squares, a list of numbers
;;; Preconditions:
;;; [Standard]
;;; Postconditions:
;;; list-of-squares has the form (square_1 square_2 ... square_n)
;;; For all i, square_i is the square of num_i (that is num_i * num_i).
(define square-values
(lambda (lst)
(let ((square (lambda (val) (* val val))))
(if (null? lst)
null
(cons (square (car lst)) (square-values (cdr lst)))))))
a. Verify that square-values works
correctly.
b. Try to execute square outside of square-values.
Explain what happens.
Here is a procedure that takes a non-empty list of lists as an argument and returns the longest list in the list (or one of the longest lists, if there is a tie).
;;; Procedure:
;;; longest-list-in-list
;;; Parameters:
;;; lols, a list of lists
;;; Purpose:
;;; Finds one of the longest lists in los.
;;; Produces:
;;; longest, a list
;;; Preconditions:
;;; lols is a nonempty list.
;;; every element of lols is a list.
;;; Postconditions:
;;; Does not affect lols.
;;; Returns an element of lols.
;;; No element of lols is longer than longest. That is,
;;; For each lst in lols, (length lols) >= (length lst).
(define longest-list-in-list
(lambda (lols)
; If there is only one list, that list must be the longest.
(if (null? (cdr lols))
(car lols)
; Otherwise, take the longer of the first list and the
; longest remaining list.
(longer-list (car lols) (longest-list-in-list (cdr lols))))))
This definition of the longest-list-in-list
procedure
includes a call to the longer-list procedure,
which returns
the longer of two given lists:
;;; Procedure:
;;; longer-list
;;; Parameters:
;;; left, a list
;;; right, a list
;;; Purpose:
;;; Find the longer of left and right.
;;; Produces:
;;; longer, a list
;;; Preconditions:
;;; Both left and right are lists.
;;; Postconditions:
;;; longer is a list.
;;; longer is either equal to left or to right.
;;; (>= (length longer) (length left))
;;; (>= (length longer) (length right))
(define longer-list
(lambda (left right)
(if (<= (length right) (length left))
left
right)))
Revise the definition of longest-list-in-list
so that the
name longer-list is bound to the procedure
that it denotes
only locally, in a let-expression.
Note that there are at least two possible ways to do the
previous exercise: The definiens of
longest-list-in-list can be a lambda-expression
with a let-expression as its body, or it can
be a
let-expression with a lambda-expression
as its
body. That is, it can take the form
(define longest-list-in-list
(let (...)
(lambda (los)
...)))
or the form
(define longest-list-in-list
(lambda (los)
(let (...)
...)))
a. Define longest-list-in-list in
whichever way that you did not
define it for the previous exercise.
b. Does the order of nesting affect what happens when the procedure is
invoked? You may want to use verbose-let to
help you answer
this question.
c. If there is a difference, which arrangement is better? Why?
The two definitions you came up with in the previous exercises
are not the only alternatives you have in placing the let.
Since longer-list is only needed in the
recursive case,
you can place the let there.
(define longest-list-in-list
(lambda (los)
; If there is only one list, that list must be the longest.
(if (null? (cdr los))
(car los)
; Otherwise, take the longer of the first list and the
; longest remaining list.
(let ((longer-list
(lambda (left right)
(if (<= (length right) (length left))
left
right))))
(longer-list (car los) (longest-list-in-list (cdr los)))))))
Including the original definition (in which longer-list
is
bound with define), you've now seen or
written four
variants of longest-list-in-list. Which do
you prefer?
Why?
Extend your favorite version of longest-list-in-list
so
that it verifies its preconditions (i.e., that los
only
contains lists and that los is nonempty). If
the
preconditions are not met, your procedure should return #f.
It is perfectly acceptable for you to check each list element in turn to determine whether or not it is a list, rather than to check them all at once, in advance.
Dr. Scheme provides a keyword, time,
that reports various metrics for the time it takes to execute an
expression.
Using DrScheme's (time exp)
operation, determine
which of the four versions of longest-list-in-list
is
indeed the fastest.
In doing this testing, you should build a fairly long outer list. The inner lists can be mostly 0- or 1-element lists.
Janet Davis (davisjan@cs.grinnell.edu)
Created February 15, 2007 based on http://www.cs.grinnell.edu/~davisjan/csc/151/2006F/labs/16.local_bindings.html