Input/Output


Touretzky Reading: Chapter 9

Touretzky Exercises: 9.2, 9.5, 9.8


I have selected the exercises for chapter 7 now (lecture 6).

Touretzky Exercises: 7.2, 7.4, 7.7, 7.14, 7.16, 7.18, 7.27

I, frankly, wasn't as happy with his exercises in this chapter.
Before we can do much I/O we need to introduce the indea of "string" which is not too different than the data type of the same name in other languages. First let make sure that we understand that strings are different from symbols. Consider this: > "this is string" "this is string" > 'this is a symbol THIS > Clearing input from *debug-io* >>Error: The symbol IS has no global value There is a predicate for finding out if an object is a symbol or not: STRINGP. Examples: > (stringp "foobar") T > (stringp 'foobar) NIL > (symbolp "grok") NIL Keep in mind that strings evaluate to themselves, and are not converted to uppercase like symbols. They suffer from the some of the same weirdnesses that numbers do with respect to eq: > (eq "foo" "foo") NIL > (equal "foo" "foo") T > (equal "foo" "FOO") NIL There are some other equal primitives that can do comparisons on strings without consideration to capitalization.

FORMAT

You need to know about strings because the function FORMAT takes a string as one of its arguments. FORMAT is roughly analagous to the C language functrion fprintf. FORMAT's first argument is where to write the data (more on this later). If you supply a T as this parameter, format writes its output the standard output. Here is an example: > (format t "testing 1 2 3") testing 1 2 3 NIL Note the return value of FORMAT is always NIL. There are various control parameters to the FORMAT function all of which begin with a twiddle character (~). The most important of these are ~% (carriage return) and ~S format S-expression. The latter of which causes the FORMAT function to expect another argument which is an sexpression for display. Consider: > (format t "testing 1~%2~%3") testing 1 2 3 NIL > (format t "the value is ~S" '(a b c)) the value is (A B C) NIL > (format t "the superbowl matches ~S against ~S" 'san-fran 'san-diego) the superbowl matches SAN-FRAN against SAN-DIEGO NIL The sexpressions are placed where the ~S appear in the format expression and are place in order. There are some other fun command strings for FORMAT which include ~D to format decimal numbers and ~F to format floating point numbers. Full listings of all these are in the Steele book.

What's the matter here?

What's wrong with this code: (defun bogus-abs-function (x) (if (> x 0) (format t "x is positive (~S)" x) (format t "x was negative (~S) but we fixed it" x) (- 0 x))) Ok, the problem here is that the IF conditional only evaluates one s-expression as its else clause (and its then clause for that matter) and we have two. This a problem, because the format function doesn't really compute anything, it produces output. This is called a function that is being invoked "for effect." (We are evaluating not for its computation, but for its effect.) Note that this means that we are programming in a strictly functional sense anymore.

The way to avoid this is through (naturally!) another function called PROGN. This function evaluates its arguments and returns the result of the last one. Another way of saying this is that all of the sexpressions passed to PROGN are evaluated "for effect" except the last one. So now we can write a correct version of the absolute value function with some FORMAT statements.

(defun nifty-abs (x) (if (> x 0) (progn (format t "x is positive (~S) " x) x) (progn (format t "x was negative (~S) but we fixed it" x) (- 0 x)))) Keep in mind that PROGN can have any number of arguments and that they are evaluated in order of their appearance.

There are some other versions of the PROGN called PROG1 and PROG2 which return the result of their first and second function respectively. PROGN should be used with care: the most common place to need it is in conditionals, since they have a limit on the number of forms (s-expressions) they can evaluate. DEFUN has no such limitation (because it uses PROGN of course!)

As a quick aside: If I remember correctly, a programming language must have sequencing, conditionals, and looping to be able compute all Turing machines. We now have seen the Lisp version of all these: PROGN, COND, and recursion.

READ

The function READ reads a single Lisp object (s-expression, form, whatever you want to call it) from a stream and returns it as its value. Thus if you type: > (sqrt (read t)) At a Lisp interpreter it will sit there waiting on you.(The stream that we denote with T is the stream associated with your keyboard, the "standard input." This is for convience, just like you can use T with FORMAT for the standard output.) The reason it sits there is that it is waiting for you to type something at it. I'll use a font like this for things you type at the interpreter in response to READ calls. Here we go: > (sqrt (read t)) 49 7.0 > (sqrt (read t)) 6.2 2.4899799195977464 > (cons (read t) '(b c)) a (A B C) > (car (read t)) (a b c) A Are the arguments that you type at the keyboard be evaluated? Hint: Check the car example, why didn't it return undefined function 'A'?

The World's Smallest Lisp Interpreter

(defun lisp () (progn (format t "~S~%" (eval (progn (format t ">") (read t)))) (lisp))) This will work fairly well as a Lisp interpreter until you type and error or run out of stack space (since this recursion never terminates). A real Lisp interpreter does some clever things with errors, but this is not too bad for 8 lines of code!

READ and the end of a file (or fun with EQ)

How do we know when READ is out of data. By default, READ will error on end of file, but this is controllable. You can pass extra arguments to read. The second argument to READ is whether or not it should error on end of file (EOF). If this is true (non-NIL) then the third parameter is the value to return when it finds end of file. Consider: > (read t nil 'frip-frap) frip-frap FRIP-FRAP > (read t nil 'frip-frap) FRIP-FRAP > It the second case I typed control-D which is the Unix way of telling the system you are at end of file. What's going on here? We need to be able to differentiate the return value on end of file FRIP-FRAP from the person typing FRIP-FRAP at the prompt. The key is to use the predicate EQ (its not totally useless!): (defun read-test (eof-indicator) (if (eq (read t nil eof-indicator) eof-indicator) (format t "end of file") (format t "read from keyboard" ))) This will "do the right thing" irrespective of the value of eof-indicator or the value the user types at the command line.

WITH-OPEN-FILE

WITH-OPEN-FILE is a just a macro that somebody write to make creating streams easy. This is how you get to use streams that are not T for FORMAT and READ. This function creates a variable (lexically scoped!) which is stream and is bound to a file while the forms of the WITH-OPEN-STREAM are executed. The stream is automatically closed when the WITH-OPEN-STREAM is exited (by design or by error!). The streams default to read access,if you want write access, you can do: (with-open-file (stream "/users/i/iansmith/TA/2360/grik" :direction :output) ( ... )) NIL Note that WITH-OPEN-FILE is implemented with PROGN so what value does it return?


Back To The CS2360 Home Page
Ian Smith (iansmith@cc.gatech.edu)

Last Modified 23 Jan 95 by Ian Smith (iansmith@cc.gatech.edu)