Fundamentals of Computer Science I: Media Computing (CS151.02 2007F)

Naming Values with Local Bindings

This lab is also available in PDF.

Summary: In this laboratory, you will ground your understanding of the basic techniques for naming values and procedures in Scheme, let and let*.

Contents:

Preparation

Create a new 20x20 image and name it canvas.

Exercises

Exercise 1: Evaluating let

What 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.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 five names, alpha, beta, gamma, delta, and epsilon, with alpha bound to a color of your choice (such as color.red), and each subsequent name bound to a value darker than it (computed by rgb.darker). That is, beta should be a darker version of alpha, gamma a darker version of beta, and so on and so forth. The body of the innermost let should list the five colors.

Your result will look something like

(let ((...))
  (let ((...))
    (let ((...))
      (let ((...))
        (let ((...))
	  (list alpha beta gamma delta epsilon))))))

Remember that (rgb.darker color) gives a darker version of color.

b. Name the result of the computation using define.

(define colors 
  (let ((...))
    (let ((...))
      (let ((...))
        (let ((...))
          (let ((...))
	    (list alpha beta gamma delta epsilon)))))))

c. Look at the values in that list with

> (map rgb->string colors)

d. Put some colors in canvas with

(region.compute-pixels! 
  canvas 
  0 0 
  (- (image.width canvas) 1) (- (image.height canvas) 1)
  (lambda (pos) (list-ref colors 
                          (modulo (* (position.col pos) (position.row pos))
                                  5))))

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.

b. Repeat steps b and c of the previous problem.

Exercise 4: 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-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))))

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.

(define value
  (lambda (val)
    (display "Computed: ")
    (display val)
    (newline)
    val))

a. 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))))

b. Confirm that years-to-seconds-b does not recompute the values each time it is called. Again, make changes like those reported above.

c. Given that years-to-seconds-b does not recompute each time, when does it do the computation? (Consider when you see the messages.)

Exercise 5: Ordering Bindings, Revisited

You may recall that we defined a procedure to compute a grey with the same brightness as a original color.

(define rgb.greyscale
  (lambda (color)
    (let ((brightness (+ (* 0.30 (rgb.red color)) 
                         (* 0.59 (rgb.green color))
                         (* 0.11 (rgb.blue color)))))
      (rgb.new brightness brightness brightness))))

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).

Exercise 6: Faster Blends

Here's a procedure that one might use to make a blend between two colors.

(define image.fill-with-blend!
  (lambda (image start-color end-color)
    (region.compute-pixels!
     image
     0 0
     (- (image.width image) 1) (- (image.height image) 1)
     (lambda (pos)
       (rgb.new (+ (* (position.col pos) 
                      (/ 1 (- (image.width image) 1))
                      (rgb.red end-color))
                   (* (- (image.width image) (position.col pos) 1)
                      (/ 1 (- (image.width image) 1))
                      (rgb.red start-color)))
                (+ (* (position.col pos) 
                      (/ 1 (- (image.width image) 1))
                      (rgb.green end-color))
                   (* (- (image.width image) (position.col pos) 1)
                      (/ 1 (- (image.width image) 1))
                      (rgb.green start-color)))
                (+ (* (position.col pos) 
                      (/ 1 (- (image.width image) 1))
                      (rgb.blue end-color))
                   (* (- (image.width image) (position.col pos) 1)
                      (/ 1 (- (image.width image) 1))
                      (rgb.blue start-color))))))))

a. Create a blend from magenta to yellow in a five-by-five image. Then, find the largest square image you can fill with a blend from magenta to yellow in under fifteen seconds. (You need not get an exact size, just a close approximation.)

b. Identify common expressions that are repeated in image.fill-with-blend!. When you are done, compare your list to those in the notes on this part of the problem.

c. Which of these common expressions have values that are independent of the position?

d. Write a let or let* expression to bind names to these expressions. Make sure that this let encloses the call to region.compute-pixels!.

When you are done, compare your answer to the notes on this part of the problem.

e. Run the modified code on a five-by-five image to see if it appears to be any faster. Then, find the largest square image you can fill with a blend from magenta to yellow in under fifteen seconds. (Again, you need not get an exact size, just a reasonable approximation.)

f. Which of the common expressions from step b have values that depend on the position?

g. Write a let or let* expression to bind names to those pieces of code. For example, since (position.col pos) appears three times, you might name it col.

When you are done, compare your answer to the notes on this part of the problem.

h. Run the modified code on the same image size as you came up with for step e see if it appears to be any faster.

i. There are a few expressions, such as (rgb.red end-color), that appear only once, but that have an identical value each time the inner lambda is applied. Name those expressions in the outer let.

j. Compare your final version to the notes on this part of the problem.

k. What advantages do you see to this final version?

For Those With Extra Time

Extra 1: Finding the Leftmost Spot

Here is a procedure that takes a non-empty list of spots as an argument, and returns the leftmost spot in the list (or one of the leftmost spots, if there is a tie).

;;; Procedure:
;;;   spots.leftmost
;;; Parameters:
;;;   spots, a list of spots
;;; Purpose:
;;;   Finds the leftmost of spots.
;;; Produces:
;;;   leftmost, a spot
;;; Preconditions:
;;;   spots is nonempty.
;;; Postconditions:
;;;   leftmost is an element of spots.
;;;   For every spot, spot, in spots
;;;     (spot.col leftmost) <= (spot.col spot)
(define spots.leftmost
  (lambda (spots)
    (if (null? (cdr spots))
        (car spots)
        (spot.leftmost (car spots) (spots.leftmost (cdr spots))))))

The definition of spots.leftmost includes a call to the spot.leftmost procedure, which returns the leftmost of two spots.

;;; Procedure:
;;;   spot.leftmost
;;; Parameters:
;;;   spot1, a spot
;;;   spot2, a spot
;;; Purpose:
;;;   Finds the leftmost of the two spots.
;;; Produces:
;;;   leftmost, a spot
;;; Preconditions:
;;;   [No additional preconditions.]
;;; Postconditions:
;;;   leftmost is spot1 or spot2.
;;;   (spot.col leftmost) <= (spot.col spot1)
;;;   (spot.col leftmost) <= (spot.col spot2)
(define spot.leftmost
  (lambda (spot1 spot2)
    (if (<= (spot.col spot1) (spot.col spot2))
        spot1
        spot2)))

Using a let expression, revise the definition of spots.leftmost so that the name spot.leftmost is bound to the procedure that it denotes only locally.

Extra 2: The Leftmost Spot, Revisited

Note that there are two ways to do the previous problem. You can nest the lambda within the let, as in

(define spots.leftmost
  (let (...)
    (lambda (spots)
      ...)))

or you can nest the let within th elambda, as in

(define spots.leftmost
  (lambda (spots)
    (let (...)
      ...)))

a. Define spots.leftmost 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?

c. If there is a difference, which arrangement is better? Why?

Notes

Notes on Problem 6b

Here are some duplicated expressions. You may have noted others.

The following expressions are not duplicated. Nonetheless, you will find it useful to name them. You will be asked to do so in a later step of this exercise.

Return to the problem.

Notes on Problem 6d

(define image.fill-with-blend!
  (lambda (image start-color end-color)
    (let* ((width (image.width image))
           (last-col (- width 1))
           (frac (/ 1 last-col)))
      (region.compute-pixels!
       image
       0 0
       last-col (- (image.height image) 1)
       (lambda (pos)
         (rgb.new (+ (* (position.col pos) 
                        frac
                        (rgb.red end-color))
                     (* (- width (position.col pos) 1)
                        frac
                        (rgb.red start-color)))
                  (+ (* (position.col pos) 
                        frac
                        (rgb.green end-color))
                     (* (- width (position.col pos) 1)
                        frac
                        (rgb.green start-color)))
                  (+ (* (position.col pos) 
                        frac
                        (rgb.blue end-color))
                     (* (- width (position.col pos) 1)
                        frac
                        (rgb.blue start-color)))))))))

Return to the problem.

Notes on Problem 6g

(define image.fill-with-blend!
  (lambda (image start-color end-color)
    (let* ((width (image.width image))
           (last-col (- width 1))
           (frac (/ 1 last-col)))
      (region.compute-pixels!
       image
       0 0
       last-col (- (image.height image) 1)
       (lambda (pos)
         (let* ((col (position.col pos))
                (loc (- width col 1)))
           (rgb.new (+ (* col frac (rgb.red end-color))
                       (* loc frac (rgb.red start-color)))
                    (+ (* col frac (rgb.green end-color))
                       (* loc frac (rgb.green start-color)))
                    (+ (* col frac (rgb.blue end-color))
                       (* loc frac (rgb.blue start-color))))))))))

Return to the problem.

Notes on Problem 6j

(define image.fill-with-blend!
  (lambda (image start-color end-color)
    (let* ((width (image.width image))
           (last-col (- width 1))
           (frac (/ 1 last-col))
           (start-red (rgb.red start-color))
           (end-red (rgb.red end-color))
           (start-green (rgb.green start-color))
           (end-green (rgb.green end-color))
           (start-blue (rgb.blue start-color))
           (end-blue (rgb.blue end-color)))
      (region.compute-pixels!
       image
       0 0
       last-col (- (image.height image) 1)
       (lambda (pos)
         (let* ((col (position.col pos))
                (loc (- width col 1)))
           (rgb.new (+ (* col frac end-red)
                       (* loc frac start-red))
                    (+ (* col frac end-green)
                       (* loc frac start-green))
                    (+ (* col frac end-blue)
                       (* loc frac start-blue)))))))))

Return to the problem.

 

History

 

Disclaimer: I usually create these pages on the fly, which means that I rarely proofread them and they may contain bad grammar and incorrect details. It also means that I tend to update them regularly (see the history for more details). Feel free to contact me with any suggestions for changes.

This document was generated by Siteweaver on Mon Dec 3 09:55:04 2007.
The source to the document was last modified on Tue Oct 16 11:51:42 2007.
This document may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2007F/Labs/let-lab.html.

You may wish to validate this document's HTML ; Valid CSS! ; Creative Commons License

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright © 2007 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (Selected materials copyright by John David Stone and Henry Walker and used by permission.) This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation. This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.