Output can be written to a file or to the screen and input can be read either from a file or from the keyboard.
We might want to print out other data than just the final result of a computation. For example, we might want to print out intermediate results as a means of debugging our programs if they don't behave as they should. Scheme provides a couple of procedures for handling input, output and the ports related to the issue.
If we want to write to the screen we can use the procedures display or write. There is also a procedure newline for printing and end of line.
The procedure display is called with one argument, which it prints to the screen as a side effect, returning an unspecified value. For example:
(define square (lambda (x) (* x x))) (define test (lambda () (begin (display "Scheme is great: ") (display (square 2)) (newline)))) > (test) Scheme is great: 4
Note that display should never be used just to print out the value returned by a procedure. It is only a tool for writing additional information on the screen. For example, let's look at how to write the procedure square and how not to:
(define square (lambda (x) (* x x))) (define horror-square (lambda (x) (let ((result (* x x))) (begin (display result) (newline)))))
Now, let's see them both in action:
> (square 5) 25 > (horror-square 5) 25 > (define a (square 5)) > a 25 > (define b (horror-square 5)) 25 > b >
What happened was that square behaved as we expected it to, but horror-square behaves completely differently. They both print out the expected result when called directly, however, the result printed by square was a return value, whereas the result printed by horror-square was caused by a side effect. The return value of the procedure horror-square is unspecified. Trying to bind the result of the procedure square to a variable works fine and the variable can be used later on. Trying the same using horror-square will clearly not work as we might wish. Hence, you cannot really use horror-square to anything else than printing out the square of its argument--it can never be called by another procedure needing the square of a number. Say goodbye to reusability, modularity and good programming style! Therefore, don't ever use display to print out values that should be return values! The same applies to write or any other procedure causing side effects.
Write is almost like display, but strings that are in the written representation are enclosed in double quotes:
(define test2 (lambda () (begin (write "Scheme is great: ") (write (square 2)) (newline)))) > (test2) "Scheme is great: "4
> (begin (display "foobar") (newline)) foobar > (begin (write "foobar") (newline)) "foobar"
The procedure write is usually used together with the procedure read. Note that if you write something using write and then read the output of write using read, then you obtain the same thing that you wrote. The procedure write produces a written representation of its argument and is meant for producing machine readable output, whereas display is meant for producing human readable output.
Scheme also provides a couple of procedures for reading from the keyboard. The procedure read returns the value of the next parsable object that is entered on the keyboard. For example, let's write a procedure that reads input from the keyboard using read and then prints out the type of argument together with the argument:
(define blopp (lambda () (let ((a (read))) (cond ((number? a) (show "Number" a)) ((char? a) (show "Character" a)) ((string? a) (show "String" a)) ((symbol? a) (show "Symbol" a)) ((list? a) (show "List" a)) (else (show "Unknown type" a))) (blopp))) (define show (lambda (type arg) (begin (display type) (display ": ") (display arg) (newline))))
The procedure works in the following way:
> (blopp) 5 Number: 5 #\a Character: a "foobar" String: foobar b Symbol: b (1 2 3 4) List: (1 2 3 4) #(1 2 3 4) Unknown type: #(1 2 3 4)
What was left unmentioned in the previous section was that the data read came from an input port and the data written was sent to an output port. Ports are Scheme objects associated with input and output devices, the standard input being the keyboard and the standard output being the screen by default. It is also possible to associate ports with files. This is done with the Scheme procedures open-input-file and open-output-file. Both take one argument, namely the name of the file to be opened, e.g.
>(open-input-file "test.txt")
Ports can be closed with close-input-port and close-output-port, which both take the port as an argument, e.g.
(define p (open-input-file "test.txt")) ... (close-input-port p)
Suppose that we want to read data from a file testdata.txt. What we need to do is open an input port associated to the file to be read and assign it to a variable p. We also need a procedure to read the data. We can use the procedure read that was mentioned in the previous section by providing it with one extra argument, namely the input port p. Say that our file contains the following:
Scheme is fun! ((a b) (1 2))
Let's now read the contents of that file:
> (define p (open-input-file "testdata.txt")) > (read p) scheme > (read p) is > (read p) fun! > (read p) ((a b) (1 2)) > (read p) #<eof> > (close-input-port p) > (read p) #<primitive:get-port-char>: input port is closed >
Scheme also provides a procedure eof-object?, which has the following syntax:
(eof-object? obj)
If obj is an end of file object, then #t will be returned, otherwise #f will be returned. The procedures read, read-char and peek-char return an eof object when they encounter the end of the file they are reading.
The procedure char-ready? has the following syntax:
(char-ready? port)
The port may be omitted, in which case it defaults to the value returned by current-input-port. The procedure char-ready? returns #t if a character is ready on the input port, otherwise it returns #f. If #t is returned, then it is guaranteed that the next read-char operation on the given port will not hang.
There is also a procedure read-char that reads and returns the following character available. If no characters are available, an end of file object will be returned. The procedure peek-char is almost like read-char, but it does not update the port (more on ports will follow) to point to the next character. Hence, read-char and peek-char will return the same character the first time they are called, but on the next call to peek-char the same value will be returned as from the previous call. For example, let's see how read-char and peek-char work:
> (define in (open-input-file "testdata.txt")) > (read-char in) #\S > (read-char in) #\c > (read-char in) #\h > (read-char in) #\e > (read-char in) #\m > (read-char in) #\e > (define peek-in (open-input-file "testdata.txt")) > (peek-char peek-in) #\S > (peek-char peek-in) #\S > (read-char peek-in) #\S > (read-char peek-in) #\c > (peek-char peek-in) #\h
To write to a file we need to associate an output port with a file. This is done in the following way:
(open-output-file "test.txt")
We can also assign the port returned from the expression above to a variable:
(define p (open-output-file "test.txt"))
The port is usually opened within a procedure, in which case let should be used instead of define:
(let ((p (open-output-file "test.txt"))) body)
If the file does not exist, it will be created. If the file does exist, the behaviour is implementation dependent. The old file might be deleted and a new one created.
The procedure with-output-to-file can also be used to write to files, for example, let's write a procedure which writes a list to a file so that each item of the list is followed by a newline:
(define display-list (lambda (ls) (if (null? ls) (newline) (begin (display (car ls)) (newline) (display-list (cdr ls)))))) (define write-list-to-file (lambda (filename ls) (with-output-to-file filename (lambda () (display-list ls)))))
Let's see an example:
> (write-list-to-file "testfile.tmp" '(this is a test!))
The file we wrote to now looks like this:
this is a test!
There are various procedures for writing to and reading from files. For more
information, see .