Laboratory Exercises For Computer Science 153

Input and Output

Input and Output

Goals: This laboratory exercise considers output procedures to aid in the tracing of a program. This lab also discusses interactive Scheme programming, using read, display and write statements. Finally, the lab mentions the use of sentinel values to halt the reading of data from the keyboard.

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


(define simple-add2
   (lambda (L)
   ;Pre-condition:  L is a list of numbers
   ;Post-condition:  returns a new list where 2 is added to each value in 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))
The lab on tail recursion introduced the trace-define statement as one way to help trace the execution of a procedure. However, that facility only prints values at the start and end of each procedure call. Sometimes, we may want intermediate values printed as well. To explore how this might be done, consider the following recursive procedure simple-add2, and suppose we 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)
   ;Pre-condition:  L is a list of numbers
   ;Post-condition:  returns a new list where 2 is added to each value in L
      (display L) (newline) ;;NEW: print out current list on separate line
      (if (null? L)
          '()
          (cons (+ 2 (car L)) (simple-add2-trace (cdr L))))
   )
)
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.
  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. 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.

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

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

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

  5. 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)
   ;Pre-condition:  L is a list of numbers
   ;Post-condition:  returns a new list where 2 is added to each value in 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)
   ;Pre-condition:  L is a list of numbers
   ;Post-condition:  returns a new list where 2 is added to each value in 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)
   ;Pre-condition:  L is a list of numbers
   ;Post-condition:  returns a new list where 2 is added to each value in 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.
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)
   ;Pre-condition:  L is a list of numbers
   ;Post-condition:  returns a new list where 2 is added to each value in 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.

Reading and Writing:

Sometimes, in addition to printing, it is convenient to read values from the keyboard. This is accomplished with the read procedure with returns with the value of the next item typed at the keyboard. The use of this procedure is illustrated in the solution to the following simple problem

Problem: Write a procedure that reads 3 scores from the keyboard and averages them:

Solution: The read procedure gets one value from the keyboard; thus, we use this procedure 3 times in our computation:


(define average-3
   (lambda ()
   ;Pre-condition:  None
   ;Post-condition:  returns the average of 3 numbers read from the keyboard
       (display "Enter three numbers:  ")
       (display (/ (+ (read) (read) (read)) 3.0))
       (display " is the average of the three numbers")
       (newline)
   )
)
When we run this program to average the numbers 3, 5, and 10, we might have the following interaction with the computer:

> (average-3)
Enter three numbers:  3   5  10
6.0 is the average of the three numbers
Here, we began processing by calling the procedure with (average-3). Note that this procedure does not require any parameters.

When the program runs, the first display procedure prompted the user to enter some numbers. Then, before the next display can finish, three values must be read and averaged. Each call to procedure read fetches the next value, and the average then is computed.

  1. What happens if you enter three fractions or some negative numbers when you run this procedure?

  2. What happens if you enter a string instead of a number when you run this procedure?

Suppose you wanted to average any number of values; you might ask the computer to continue reading numbers until you entered a 0. In this context, you could pass the current sum and the number of values read to the kernel procedure. The base case would be identified by checking if the current value read was zero.

Write an average procedure which reads successive numbers until 0 is entered and computes and prints the average of these numbers. Be sure you do not include 0 in your computation of the average.

Note: In this problem, the number 0 is called a sentinel. Generally, a sentinel is a value read that tells the computer something special about how processing should proceed.

Formatted Output:

Sometimes we may wish to format our output. For example, we may want to create the following table of square roots:
Number   Square Root
   1       1.00000
   2       1.41421
   3       1.73205
   4       2.00000
   5       2.23607
Here, the procedure asks the user to provide a number n and then prints the square roots from 1 to n. In this work, the title is printed only once, while the other lines involve different numbers. Thus, in our first attempt, we write a main procedure for the title and a helping function for the lines:
(define sqrt-table
   (lambda (n)
      (display "Number   Square Root")
      (newline)
      (sqrt-table-helper n)
   )
)

(define sqrt-table-helper
   (lambda (n)
      (if (>= n 1)
          (begin
             (sqrt-table-helper (- n 1))
             (display n)
             (display (sqrt n))
             (newline))
      )
   )
)
While this approach almost works, the format of the result is not acceptable:
Number   Square Root
11
21.4142135623730951
31.7320508075688772
42
52.23606797749979
In reviewing this output, at least two difficulties are apparent. First, the two numbers (n and its square root) are printed next to each other. Second, the number of decimal places for each value varies. The solution to such troubles is to specify the format of the output.

Generally, Scheme solves this problem by providing many approaches to convert numbers to strings and to manipulate strings. However, Scheme does not have standard procedures to for formatted output. For this course, therefore, we will use a customized package ~walker/151s/labs/write-formatted.ss.

To use this package, load it in your Scheme work:

(load "~walker/151s/labs/write-formatted.ss")
Then use procedures write-int and write-real for formatting integer and real numbers, respectively. The write-int procedure asks for the number to be printed and the width of the field for printing. In our example, we want 4 spaces for our integer (3 blank spaces plus the digit). Thus, we write
(write-int n 4)
Similarly, for real numbers, we supply write-real with the number to be printed, the total width for printing the number, and the desired number of decimal places. In this example, the number takes 14 characters overall, with 5 decimal place accuracy:
(write-real (sqrt n) 14 5)
  1. Replace the display statements in sqrt-table-helper by the corresponding write-int and write-real statements, and check that the new formatting works as required.

  2. Run write-int with various integers and widths to check that it runs correctly. What happens if write-int is given a width that is narrower than required by the number to be printed (e.g., 2 spaces allocated for the printing of 456)?

  3. Run write-real with a several real numbers, widths, and fractional-lengths to check that it also runs correctly.


This document is available on the World Wide Web as

http://www.math.grin.edu/~walker/courses/153.sp00/lab-i-o.html

created March 5, 1997
last revised January 10, 2000