CSC151.01 2009F Functional Problem Solving : Labs

Laboratory: Conditionals


Summary: In this laboratory, you will explore three of Scheme's primary conditional control operations, if, when, and cond.

Reference

If expressions typically have the form

(if test consequent alternative)

The Scheme interpreter first evaluates the test. If the value of the test is false (that is, #f), the interpreter evaluates the alternative and returns the value. If the value of the test is truish (that is, anything that is not false), the interpreter evaluates the consequent and returns its value.

When expressions typically have the form

(when
  test
  exp1
  ...
  expn)

The Scheme interpreter first evaluates the test. If the value of the test is false (that is, #f), the interpreter ignores the rest of the expression. If the value of the test is truish, the interpreter evaluates the expressions in turn.

Cond expressions typically have the form

(cond
  (test0 exp0)
  (test1 exp1)
  ...
  (testn expn)
  (else alternative))

Scheme evaluates each test in turn until one is truish. It then evaluates the corresponding expression and returns its value. If none of the tests is truish, then it evaluates the alternative and returns its value.

Preparation

a. Add the following to your definitions pane to make it easy to create and show a variety of sample images. (Make sure to replace username with your user name.)

(load "/home/username/Desktop/library.scm")

(define grid (image-show (image-new 3 3)))
(define canvas (image-show (image-new 200 200)))
(define picture 
  (image-show (image-load "/home/rebelsky/MediaScheme/Images/kitten.png")))

(image-set-pixel! grid 0 0 (rgb-new 192 0 0))
(image-set-pixel! grid 1 0 (rgb-new 192 0 192))
(image-set-pixel! grid 2 0 (rgb-new 0 0 192))
(image-set-pixel! grid 0 1 (rgb-new 192 192 0))
(image-set-pixel! grid 1 1 (rgb-new 0 192 192))
(image-set-pixel! grid 2 1 (rgb-new 255 255 255))
(image-set-pixel! grid 0 2 (rgb-new 0 192 0))
(image-set-pixel! grid 1 2 (rgb-new 0 0 0))
(image-set-pixel! grid 2 2 (rgb-new 192 192 192))

b. Save your definitions pane as conditionals-lab.scm.

c. Open the reference page for drawings in a separate window or tab.

Exercises

Exercise 1: Comparing Brightness, Revisited

Recently, we've considered a number of activities that required us to work with the brightness of colors. We typically compute brightness using an expression which can be encapsulated in a procedure as follows:

;;; Procedure:
;;;   rgb-brightness
;;; Parameters:
;;;   color, an RGB color
;;; Purpose:
;;;   Computes the brightness of color on a 0 (dark) to 100 (light) scale.
;;; Produces:
;;;   b, an integer
;;; Preconditions:
;;;   color is a valid RGB color.  That is, each component is between
;;;     0 and 255, inclusive.
;;; Postconditions:
;;;   If color1 is likely to be perceived as lighter than color2,
;;;     then (brightness color1) > (brightness color2).
;;;   0 <= b <= 100
(define rgb-brightness
  (lambda (color)
    (round (* 100 (/ (+ (* 0.30 (rgb-red color))
                        (* 0.59 (rgb-green color))
                        (* 0.11 (rgb-blue color)))
                      255)))))

a. Add rgb-brightness to your library and then reload your library.

b. Using rgb-brightness as a helper, write a procedure, (rgb-brighter? color1 color2), that returns true if color1 is brighter than color2 and false otherwise.

c. Using rgb-brightness or rgb-brighter? as a helper, write a procedure, (rgb-brighter-2 color1 color2), that returns the brighter of color1 and color2.

d. What do you expect the following to accomplish?

> (image-transform! grid rgb-brighter-2)

e. Check your answer experimentally.

f. As you may have noted, the transformation was invalid because rgb-brighter-2 expects two colors, not one. Let's try an appropriate call to rgb-brighter-2. What do you expect the following to accomplish?

> (image-transform! grid (r-s rgb-brighter-2 (rgb-new 255 0 0)))

g. Check your answer experimentally. You may want to zoom in on grid first, so that the changes are clearer.

f. Add rgb-brighter? and rgb-brighter-2 to your library.

Exercise 2: Shades of Grey

Consider the following four colors:

(define grey0 (rgb-new 0 0 0))
(define grey1 (rgb-new 96 96 96))
(define grey2 (rgb-new 192 192 192))
(define grey3 (rgb-new 255 255 255))

a. Using if expressions (not cond expressions), write a procedure, (rgb-4grey color), that returns

  • grey0, if the brightness of color is less than 25;
  • grey1, if the brightness of color is at least 25 but less than 50;
  • grey2, if the brightness of color is at least 50 but less than 75; or
  • grey3, if the brightness of color is at least 75.

b. Build and show a variant of grid using rgb-4grey. (Testing our procedure on a small image is a good way to make sure that there are no glaring errors.)

c. Build and show a variant of picture using rgb-4grey.

d. Rewrite rgb-4grey so that it uses cond rather than if.

When you are done with this exercise, you may want to look at the notes on this exercise.

Exercise 3: Safer Selections

a. What do you expect the following expressions to accomplish?

> (image-select-ellipse! canvas REPLACE -50 0 40 40)
> (image-select-rectangle! canvas REPLACE 10 220 50 50)

b. Check your answer experimentally.

c. As you should have observed, both of those expressions generate an error because the selection is outside the bounds of the image. Write a new predicate, (image-contains-selection? img left top width height), that determines whether the given selection is likely to be within img.

Hint: One case in which a selection is invalid is when the left edge of the selection is greater than or equal to the width of the image.

Hint: Another case in which a selection is invalid is when the right edge (given by adding the width to the left edge) is less than 0.

d. Now that you have a mechanism for determining the likely safety of selections, write a new procedure (image-safely-select-ellipse! img operation left top width height) that checks whether the selection is likely to be within the bounds of the image and, if so, does the selection using image-select-ellipse!. If the selection is unlikely to be within the bounds of the image, image-safely-select-ellipse! should select nothing.

Hint: You may want to use when to make your decision.

Exercise 4: Rendering Drawings

Here is the drawing-render! procedure from the reading.

;;; Procedure:
;;;   drawing-render!
;;; Parameters:
;;;   drawing, a drawing value
;;;   image, an image id
;;; Purpose:
;;;   Render drawing on the image.
;;; Produces:
;;;   [Nothing; called for side effect]
;;; Preconditions:
;;;   drawing can be rendered on image.  
;;; Postconditions:
;;;   drawing has been rendered on image.
(define drawing-render!
  (lambda (drawing image)
    (cond
      ((equal? (drawing-type drawing) 'rectangle)
       (context-set-fgcolor! (drawing-color drawing))
       (image-select-rectangle! image REPLACE
                                (drawing-left drawing)
                                (drawing-top drawing)
                                (drawing-width drawing)
                                (drawing-height drawing))
       (image-fill-selection! image))
      ((equal? (drawing-type drawing) 'ellipse)
       (context-set-fgcolor! (drawing-color drawing))
       (image-select-ellipse! image REPLACE
                              (drawing-left drawing)
                              (drawing-top drawing)
                              (drawing-width drawing)
                              (drawing-height drawing))
       (image-fill-selection! image))
      (else
       (image-draw-line! image 0 0 (image-width image) (image-height image))))))

And here are two sample drawings.

(define d1
  (drawing-hshift
   (drawing-vshift
    (drawing-scale
     (drawing-recolor
      drawing-unit-square
      "red")
     50)
    40)
   30)
  )

(define d2
  (drawing-vscale
   (drawing-hscale
     (drawing-recolor
      drawing-unit-circle
      "green")
    60)
   120)
  )

a. Test drawing-render! to verify that it does, in fact, render both drawings.

b. As you may have observed, we don't immediately see the rendered drawing. Update drawing-render! so that, after rendering, it updates the display and selects nothing.

c. Extend drawing-render! so that it outlines the drawing in black. (Recall that you can stroke the drawing with image-stroke! to outline the image.)

d. Extend drawing-render! so that it outlines the drawing in black if the drawing is a light color and outlines the drawing in grey if the drawing is a dark color. (You can choose your own metric for dark and light.)

For Those With Extra Time

Those with extra time may choose to do the extra problems, which focus on programming tasks, or the explorations, which focus on the application of procedures to image creation.

Extra 1: Shades of Grey, Revisited

As you may recall from the reading on Boolean values and predicate procedures, it is possible to use and and or to achieve conditional behavior. The subsequent reading on conditionals provides some additional information on the use of and and or.

Using those techniques, rewrite rgb-4grey so that it uses neither if nor cond. That is, you'll need to find ways to express the same outcome using and and or.

Extra 2: Safely Rendering Drawings

One problem with drawing-render! is that it causes an error if we try to render a drawing outside of the image. For example,

> (define canvas (image-new 10 10))
canvas
> (drawing-render! (drawing-hshift drawing-unit-circle -2) canvas)
Error: image-select-ellipse!: selection is outside of the bounds of the image 
> (drawing-render! (drawing-hshift drawing-unit-circle 11) canvas)
Error: image-select-ellipse!: selection is outside of the bounds of the image 

Rewrite drawing-render! to that it only renders drawings that fall within the bounds of the canvas.

Extra 3: Preserving Context

Consider the following procedure that draws a simple logo in a color and position of the user's choice.

;;; Procedure:
;;;   simple-logo!
;;; Parameters:
;;;   img, an image
;;;   left, a real number
;;;   top, a real number
;;;   color, a color
;;; Purpose:
;;;   Renders a simple logo on img, with the left edge of the logo
;;;   at left, the top edge at top.  Uses color as the primary color
;;;   of the logo
;;; Produces:
;;;   img, the same image
;;; Preconditions:
;;;   0 <= left < (width img) [unverified]
;;;   0 <= top < (height img) [unverified]
;;; Postconditions:
;;;   img now contains a rendering of the simple logo.
(define simple-logo!
  (lambda (img left top color)
    ; Save appropriate parts of the context
    (let ((saved-color (context-get-fgcolor))
          (saved-brush (context-get-brush)))
      ; Select the logo
      (image-select-rectangle! img REPLACE left top 50 50)
      (image-select-ellipse! img SUBTRACT 
                             (+ left 15) (+ top 10)
                             25 25)
      (image-select-ellipse! img SUBTRACT
                             (- left 5) (- top 10)
                             30 30)
      (image-select-ellipse! img INTERSECT
                             left top
                             60 60)
      ; Fill it with the main color
      (context-set-fgcolor! color)
      (image-fill! img)
      ; Outline it with a fuzzy brush to get an intersting effect
      (context-set-brush! "Circle Fuzzy (09)")
      (image-stroke! img)
      ; Restore the context, as best as is possible
      (image-select-nothing! img)
      (context-set-brush! saved-brush)
      (context-set-fgcolor! saved-color)
      ; Return the updated image
      img)))

a. Make sure that you understand what the procedure does by rendering a few copies of the logo on canvas.

b. As you may have observed, this procedure tries to “preserve” the context by grabbing the color and brush before changing them, and restoring them to the old values after it has changed them. While this strategy works fine if we're only drawing one or two logos, it can add unnecessary work if we are drawing a lot of logos in the same color.

Rewrite simple-logo! so that it only restores the brush and color if they are different from the brush and color used to create the logo. Consider using when to make decisions about whether or not to set and restore the brush and color.

c. If we don't care about the current brush and color (or if we are changing them a lot), we may want to give a hint to the programmer that she need not restore the brush and color. MediaScheme includes three procedures that do just that.

  • (context-preserve?) checks whether or not procedures are encouraged to preserve the context.
  • (context-preserve-off!) makes context-preserve? return false (#f).
  • (context-preserve-on!) makes context-preserve? return true (#t).

Rewrite simple-logo! so that it only restores the color and brush if context-preserve? returns true.

Extra 4: Safer Selections, Revisited

In exercise 3, you wrote a procedure, image-safely-select-ellipse! that first checked whether the selection was in the image and, if not, simply avoided the selection operation. However, the choice to ignore the selection may not always be appropriate. For example, if the new selection is to replace the prior selection, we should probably select nothing. In contrast, if the new selection is to be subtracted from the current selection, we should maintain the current selection.

Rewrite image-safely-select-ellipse! to do what you consider appropriate for each of the five cases:

  • The selection is valid (in which case, you can just use image-select-ellipse!)
  • The selection is not valid, and the type is REPLACE
  • The selection is not valid, and the type is ADD
  • The selection is not valid, and the type is SUBTRACT
  • The selection is not valid, and the type is INTERSECT

Explorations

Explorations are intended for students interested in further exploring the design aspects of these techniques. They also provide students who finish early with extra activities that may challenge them in different ways.

Exploration 1: Rendering, Revisited

You've already noted that drawing-render! can do more than simply select an area and then fill it. For example, we can stroke the drawing after filling it. (We could even stroke it multiple times.)

Write a procedure, drawing-render-excessively!, that renders a drawing in a more interesting manner. You might, for example, stroke the drawing with a large brush, a medium brush, and a small brush, each of different colors. You might draw a contrasting smaller shape within the object. (E.g., within a square of edge length d, you can draw a circle of diameter d; within a circle of diameter d, you can draw a square of edge length (/ d (sqrt 2).)

Notes on the Exercises

Notes on Exercise 2: Shades of Grey

The if-based solution will look something like the following.

(define rgb-4grey
  (lambda (color)
    (if (< (rgb-brightness color) 25)
        grey0
        (if (< (rgb-brightness color) 50)
            grey1
            (if (< (rgb-brightness color) 75)
                grey2
                grey3)))))

The cond-based solution will look something like the following.

(define rgb-4grey
  (lambda (color)
    (cond 
      ((< (rgb-brightness color) 25)
       grey0)
      ((< (rgb-brightness color) 50)
       grey1)
      ((< (rgb-brightness color) 75)
       grey2)
      (else
       grey3))))

Return to the problem.

Creative Commons License

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright (c) 2007-9 Janet Davis, Matthew Kluber, Samuel A. Rebelsky, and Jerod Weinman. (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.