Chapter 12. Working with Streams

Nearly every computer program you write will need to interact with the outside world at some point. Perhaps your program just needs to communicate with the user through the REPL, printing out information and capturing the user’s input from the keyboard. Other programs you write may need to read or write files on a hard drive. Additionally, you may want to write programs that interact with other computers, either over a local network or the Internet. In Common Lisp, these kinds of interactions happen through streams.

Streams are data types in Common Lisp that allow you to take some external resource and make it look like just another simple piece of data you can manipulate with your code. The external resource could be a variety of things: a file on a disk, another computer on a network, or text in a console window on the screen. As you’ll learn in this chapter, through the use of a stream, a Lisp program can interact with this outside resource just as easily as it might interact with a list or a hash table.

Types of Streams

When we communicate with an external resource from a Common Lisp program, we do so by using a stream. Different types of streams are available for different types of resources. Another factor is the direction of the stream—sometimes you will want to write data to a resource, and sometimes you will want to read data from a resource.

Streams by Type of Resource

image with no caption

When organized by the type of resource on which they operate, the following are the most commonly used stream types:

Console streams

What we’ve been using so far when communicating with the REPL.

File streams

Let us read and write to files on our hard drive.

Socket streams

Let us communicate with other computers on a network.

String streams

Let us send and receive text from a Lisp string.

Of these stream types, string streams are the black sheep of the family. Rather than letting you communicate with the outside world, string streams allow you to manipulate strings in new and interesting ways.

Streams by Direction

When you write data to a resource, you use output streams. For reading data from a resource, you use input streams.

Output Streams

Output streams are used for tasks such as writing to the REPL, writing to a file, or sending information over a socket. At the most primitive level, you can do two things with an output stream:

  • Check whether the stream is valid.

  • Push a new item onto the stream.

image with no caption

As you can see, a stream is more restrictive than a true data structure in Lisp. For instance, a list supports all of the same features as a stream (we can push a new item onto a list with push and check if a list is valid with listp), and we also can do certain tasks with a list that we can’t do with an output stream (such as changing items in the list with setf). But this limited functionality of streams actually makes them useful in many cases.

To see if we have a valid output stream, we can use the output-stream-p function. For example, the REPL has an output stream associated with it called *standard-output*. We can see if this is a valid output stream with the following code:

> (output-stream-p *standard-output*)
T

A Lisp character is one item that can be pushed onto an output stream using the basic command write-char. For example, to write the character #x to the *standard-output* stream, we can run the following command:

> (write-char #x *standard-output*)
xNIL

This code prints an x to the standard output (which, in this case, is the same as the REPL). Note that this function also returns nil, causing the x and the return value to be printed on the same line. As you saw in Chapter 6, this extra nil is just a side effect of running the code in the REPL. If we ran this command as part of a larger program, only the x would have printed out.

Note

In this chapter, we’ll discuss only streams based on text characters. In Common Lisp, you can also create streams based on other data types. For instance, if you’re working with binary data, you may want to send or receive raw bytes instead of characters. But for our purposes, manipulating textual data (and hence using streams that work with text characters) is the most convenient.

Input Streams

Input streams are used for reading data. As with output streams, the actions that you can perform with an input stream are limited. At the most primitive level, you can do two things with an input stream:

  • Check whether the stream is valid.

  • Pop an item off of the stream.

We can see if we have a valid stream with the input-stream-p command. For instance, as with standard output, the REPL has an associated input stream called *standard-input*, which we can validate as follows:

> (input-stream-p *standard-input*)
T
image with no caption

We can pop an item off the stream with the read-char command. Since we’re reading from the REPL, we need to type some characters and press enter to send the data into the standard input stream:

> (read-char *standard-input*)
123
#1

As you can see, the 1 at the front of the stream was popped off and returned by read-char.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.163.229