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)