Laboratory Exercises For Computer Science 151

Side Effects and Sequencing

Side Effects and Sequencing

Goals: This laboratory exercise considers output procedures to aid in the tracing of a program. Since any output procedure produces a side effect, this lab introduces the concept of side effects more generally. Also, as many uses of output procedures occur in the context of other processing, the general concept of the sequencing of expressions is reviewed.

Consider the following procedure to add 2 to each element in a list:


   (define simple-add2
       (lambda (L)
           (if (null? L)
               '()
               (cons (+ 2 (car L)) (simple-add2 (cdr L))))
       )
   )

  1. Check that this procedure works correctly, by running it with the command
    (simple-add2 '(2 6 -3 0 4.689))
Sometimes it is helpful to trace the execution of a procedure more carefully. For example, simple-add2 is recursive, and we might want to view the parameter L each time that simple-add2 is called. This can be accomplished by inserting procedure calls as follows:

   (define simple-add2-trace
       (lambda (L)
           (display L) (newline) ;;NEW: print out current list on separate line
           (if (null? L)
               '()
               (cons (+ 2 (car L)) (simple-add2-trace (cdr L))))
           (display L) (newline) ;;NEW: print out current list on separate line
       )
   )
Here, display is a procedure that prints out the argument that follows on your computer screen. newline moves to a new line on the screen. Thus, in the above code, the current list L will be shown at the screen on a line by itself, each time simple-add2-trace is called recursively. (While the textbook uses writeln for this purpose, writeln is not available in Chez Scheme -- but display and newline accomplish much of the same effect.)
  1. Run this revised procedure with the same data you used previously, by typing
    (simple-add2-trace '(2 6 -3 0 4.689))

  2. Write a few sentences to explain what appears at your screen:
Side Effects: While this example may suggest that the display and newline procedures can be useful for tracing the sequence of procedure calls involved in Scheme processing, it is worthwhile to note that these procedures vary from our previous view of problem solving with Scheme. Up to now, processing has involved passing information to procedures and allowing the procedures to return an answer. In this context, the answer to a problem is obtained as the result of a sequence of procedure calls. In contrast, both display and newline return the constant #<void>. That is, they do not return a useful value which will aid in solving a problem. Rather, they result in something being printed on the screen as a side effect of normal processing. The value they return rarely is of interest -- but the effect on our computer screen can be useful.

Experiment further with display and newline.

  1. Type the expression
    (list (newline)(newline)(newline)(newline)(newline))
    and explain the output.

  2. How does the output from simple-add2-trace change if a second expression (newline) is added, immediately after the first? In a sentence or two, explain why the output appears in this format.

  3. How does the output from simple-add2-trace change if the (newline) expression is removed entirely? Explain briefly.

  4. What happens if the line (display L) (newline) is moved from immediately before the if expression to immediately after it? Explain briefly.

  5. What happens if the line (display L) (newline) appears both before and after the if expression? Explain briefly.

  6. To gain additional experience with display, type in the following:
    
       (display "this is a test")
       (display (+ 2 3))
       (display 'x)
       (display x)
    
  7. In Scheme, the display procedure is designed to print only one parameter. Determine what happens when more parameters are given by typing (display 'x 'y).
Sequencing: A detailed examination of simple-add2-trace reveals that the procedure actually involves three distinct steps. These steps are clarified in the following annotated code:

   (define simple-add2-trace
       (lambda (L)
           ;;; Step 1:  print the list on the screen
              (display L)
           ;;; Step 2:  move to a new line on the screen
              (newline) 
           ;;; Step 3:  process L
              (if (null? L)
                  '()
                  (cons (+ 2 (car L)) (simple-add2-trace (cdr L))))
       )
   )
This need to execute several steps in a row arises with some frequency in Scheme. More generally, multiple statements are automatically allowed: This is sometimes referred to as an implicit begin. Scheme also contains an explicit begin expression, for when we explicitly want to perform several steps in sequence. The above example could be written with a begin expression as follows:

   (define simple-add2-trace
       (lambda (L)
           (begin
               ;;; Step 1:  print the list on the screen
                  (display L)
               ;;; Step 2:  move to a new line on the screen
                  (newline) 
               ;;; Step 3:  process L
                  (if (null? L)
                      '()
                      (cons (+ 2 (car L)) (simple-add2-trace (cdr L))))
           )
       )
   )
An explicit begin expression is required when our code involves multiple steps, but where the rules for an implicit begin do not apply. Such circumstances commonly arise within an if expression, such as the following:

   (if (fire?)
       (begin (go-outside)
              (call-fire-dept) 
       )
       (begin (get-potato-chips)
              (turn-on-tv) 
       ) 
   )
Here, the if is a special form that requires a single expression for the then and else parts; a begin must be used if multiple things must be done. As a further example, consider the following modification of simple-add2-trace>.

   (define simple-add2-trace
       (lambda (L)
           (display L)
           (if (null? L)
               (begin 
                    (display "  then clause") (newline)
                    '()
               )
               (begin 
                    (display "  else clause") (newline)
                    (cons (+ 2 (car L)) (simple-add2-trace (cdr L)))
               )
           )
       )
   )
  1. Run this revised procedure with the same data: (simple-add2-trace '(2 6 -3 0 4.689))
    Write a few sentences to explain what appears at your screen.
The textbook explains that when several expressions are executed in a begin expression, the value of the final expression is returned as the value of the begin expression. Now consider what happens when the display expression is moved to the end of each begin expression:

   (define simple-add2-trace
       (lambda (L)
           (display L)
           (if (null? L)
               (begin 
                    '()
                    (display "  then clause") (newline)
               )
               (begin 
                    (cons (+ 2 (car L)) (simple-add2-trace (cdr L)))
                    (display "  else clause") (newline)
               )
           )
       )
   )
  1. Again, run this revised procedure with (simple-add2-trace '(2 6 -3 0 4.689)), and explain the output carefully.

  2. Textbook exercises: As you have time, work on exercises 2.22-2.28 from the textbook.

Work to be turned in: