Functional Problem Solving (CSC 151 2014F) : Labs

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

Exercises

Exercise 1: 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 (irgb 0 0 255), and each subsequent value to a redder version of of the previous name. That is, beta should be a a redder version of alpha (computed by applying rgb-redder to alpha), gamma a redder version of beta (computed by applying rgb-redder to 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

(define canvas (image-new 10 10))
(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)))))))
(image-show canvas)

b. Write a similar expression, this time with row bound to the value 1 and alpha bound to (irgb 0 0 0). The remaining names should still be bound to subsequently redder versions of alpha.

Exercise 2: Simplifying Nested Lets

Write a let*-expression equivalent to the let-expression in the previous exercise, but using a different starting color and row.

When you are done, you may want to read the notes on this problem.

Exercise 3: Global vs. Local

Consider the following expression, which is a potential solution to the previous problem.

(define canvas (image-new 10 10))
; Make row 3 of canvas contain a sequence of slightly redder
; versions of the color grey.
(let* ([row 3]
       [alpha (irgb 128 128 128)]
       [beta (irgb-redder alpha)]
       [gamma (irgb-redder beta)]
       [delta (irgb-redder gamma)]
       [epsilon (irgb-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))
(image-show canvas)

a. Open a new pane, enter the traditional (require gigls/unsafe) at the top of the definitions pane, click Run, and then copy and paste the code into the interactions pane and make sure that it does what it is supposed to do.

b. What do you expect to have happen if you enter the following in the interactions pane and then rerun the code to fill in row 3?

(define row 4)

c. Check your answer experimentally. That is, put the new define statement in the interactions pane and then copy and paste the original code.

d. What do you expect to have happen if we enter the following in the interactions pane and then rerun the code to fill in row 3?

(define irgb-redder 
  (lambda (rgb)
    (rgb-new (- (irgb-red rgb) 32)
             (- (irgb-green rgb) 32)
             (+ (irgb-blue rgb) 32))))

e. Check your answer experimentally. That is, copy and paste the new definition of irgb-redder into the interactions pane, then copy and paste the expression that makes row 3 contain progressively redder values of grey.

f. Click run to restore the old definition of irgb-redder.

g. You may have noted that someone can make our code misbehave by redefining irgb-redder, but not by redefining row. Can you tell why? Suggest a way to update the expression so that it is not affected by changes to the definition of irgb-redder.

If you're not sure of your answer, you can check the notes on this problem.

Exercise 4: Detour: Printing Values

Recall that 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 evaluated. It prints the value it is called with and then returns the value.

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

The value procedure should be familiar, since it's much like the yield procedure we explored earlier.

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 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. 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 6: Ordering Bindings, Revisited

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

(define irgb-greyscale
  (lambda (color)
    (let ([component (+ (* 0.30 (irgb-red color)) 
                        (* 0.59 (irgb-green color))
                        (* 0.11 (irgb-blue color)))])
      (irgb component component component))))

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 irgb-greyscale
  (let ([component (+ (* 0.30 (irgb-red color)) 
                      (* 0.59 (irgb-green color))
                      (* 0.11 (irgb-blue color)))])
    (lambda (color)
      (irgb component component component))))

b. Explain, in your own words, why this fails (and why it should fail).

For Those With Extra Time

If you find that you have some extra time, you can try any or all of these problems, in any order you prefer.

Extra 1: Binding Transformations

Some programmers find anonymous procedures a bit too anonymous. Hence, even when they only want to use a procedure once, they still name it. However, because they want to limit the impact of creating that procedure (e.g., they don't want to conflict with someone else's procedure with a similar name), because they don't want to bother writing the six-P documentation, or because they find their code is more readable if they name procedures, they write a local definition.

For example, here's an alternate way to transform an image by dropping all but the green component.

	> (let ([only-green 
		  (lambda (color)
		    (irgb 0 (irgb-green color) 0))])
	    (image-show (image-variant picture only-green)))
	

a. Load an image of your choice and call it picture.

b. Verify that the code above works as described.

c. Write a similar expression that computes a variant in which the blue component of each pixel is 255 minus the blue component of the corresponding pixel in the original.

Extra 2: Combining Drawings

Consider the following procedure

(define drawing-munge
  (lambda (drawing)
    (let ([d0 drawing)]
      (let ([d1 (hshift-drawing 5 d0))]
        (let ([d2 (hshift-drawing 5 d1))]
          (let ([d3 (hshift-drawing 5 d2))]
            (let ([d4 (hshift-drawing 5 d3))]
              (let ([d5 (hshift-drawing 5 d4))]
                (drawing-group d0 d1 d2 d3 d4 d5)))))))))

a. Explain, in your own words, what drawing-munge does.

b. Check your answer experimentally.

c. Rewrite the expression to be more concise.

Exploration: Simplifying GIMP Tools procedures

In previous labs and homework assignments, you've written instructions that use the GIMP tools to create complex pictures (e.g., of a smiley face, a house, or a scene of your choice). Turn those instructions into a procedure, using let or let* to name the image and any other values that are used repeatedly.

Notes on the Exercises

Notes on Exercise 2

a. Here's the form of answer we expected.

(define canvas (image-new 10 10))
(let* ([row 3]
       [alpha IRGB-GRAY]
       [beta (irgb-redder alpha)]
       [gamma (irgb-redder beta)]
       [delta (irgb-redder gamma)]
       [epsilon (irgb-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))
(image-show canvas)

Notes on Exercise 3

Since we're relying on a particular definition of irgb-redder, we may want to put it in our let* statement.

(let* ([irgb-redder (lambda (color)
                       (irgb (+ 32 (irgb-red color))
                             (irgb-green color)
                             (irgb-blue color)))]
       [row 3]
       [alpha IRGB-GRAY]
       [beta (irgb-redder alpha)]
       [gamma (irgb-redder beta)]
       [delta (irgb-redder gamma)]
       [epsilon (irgb-redder delta)])
   ...)