CSC 153: Computer Science Fundamentals Grinnell College Spring, 2005 Laboratory Exercise Reading

# Input and Output

## Abstract

• output procedures to aid in the tracing of a program,
• interactive Scheme programming, using read, display and write statements, and
• the use of sentinel values to halt the reading of data from the keyboard.

## Tracing Program Execution

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

```
(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))))
)
)
```

The reading on program correctness and program design 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:

```
(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.

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

## 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:

```
(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:

• at the "top level" of a function,
• within a cond clause, and
• at the "top level" of a let (to be described in a later lab).

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:

```
(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>.

```
(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)))
)
)
)
)
```

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:

```
(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)
)
)
)
)
```

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

## Sentinel Values

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. In this circumstancethe 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.

```(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 n (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)
```

This document is available on the World Wide Web as

```http://www.walker.cs.grinnell.edu/courses/153.sp05/readings/reading-i-o.shtml
```

 created March 5, 1997 last revised February 1, 2005  For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.