9.1 Streams

I/O streams and the IO class

An input/output stream is a sequence of data bytes that are accessed sequentially or randomly. This may seem like an abstract, high-level concept -- it is! I/O streams are used to work with almost everything about your computer that you can touch, see, or hear:

  • printing text to the screen
  • receiving key-press input from the keyboard
  • playing sound through speakers
  • sending and receiving data over a network
  • reading and writing files stored on disk

All of these listed operations are considered "side-effects" in Computer Science. The touch/see/hear metric doesn't seem to work for network traffic and disk activity but side-effects are not necessarily obvious; in these two cases, something in the world has physically changed even if you can't see it.

Comparatively, "pure" code is code without side-effects: code which simply performs calculations. Of course, a "pure" program isn't very useful if it can't even print its results to the screen! This is where I/O streams come in. Ruby's IO class allows you to initialize these streams.

Example Code:

Output Window

fd, the first argument to IO.new, is a file descriptor. This is a Fixnum value we assign to an IO object. We're using a combination of the sysopen method with IO.new but we can also create IO objects using the BasicSocket and File classes that are subclasses of IO. We'll learn more about File in the next lesson.

I warned you it would feel a bit abstract! The notion of creating a "file descriptor" is inherited from UNIX, where everything is a file. Because of this, you could use the above technique to open a network socket and send a message to another computer. You wouldn't do that, of course -- you would probably use the BasicSocket (or perhaps TCPSocket) class we just mentioned.

Let's leave the abstract behind and find something a little more concrete. There are a bunch of I/O streams that Ruby initializes when the interpreter gets loaded. The list here may seem longer than if you run this locally. These examples are evaluated in the dense rubymonk environment consisting of Rails, Passenger, and all our other magic juice.

Example Code:

Output Window

Standard Output, Input, and Error

Ruby defines constants STDOUT, STDIN and STDERR that are IO objects pointing to your program's input, output and error streams that you can use through your terminal, without opening any new files. You can see these constants defined in the list of IO objects we printed out in the last example.

Example Code:

Output Window

Whenever you call puts, the output is sent to the IO object that STDOUT points to. It is the same for gets, where the input is captured by the IO object for STDIN and the warn method which directs to STDERR.

There is more to this though. The Kernel module provides us with global variables $stdout, $stdin and $stderr as well, which point to the same IO objects that the constants STDOUT, STDIN and STDERR point to. We can see this by checking their object_id.

Example Code:

Output Window

As you can see, the object_ids are consistent between the global variables and constants. Whenever you call puts, you're actually calling Kernel.puts (methods in Kernel are accessible everywhere in Ruby), which in turn calls $stdout.puts.

So why all the indirection? The purpose of these global variables is temporary redirection: you can assign these global variables to another IO object and pick up an IO stream other than the one that it is linked to by default. This is sometimes necessary for logging errors or capturing keyboard input you normally wouldn't. Most of the time this won't be necessary... but hey, it's cool to know you can do it!

We can use the StringIO class to easily fake out the IO objects. Try to capture STDERR so that calls to warn are redirected to our custom StringIO object.

Output Window

Congratulations, guest!


% of the book completed

or

This lesson is Copyright © 2011-2014 by Sidu Ponnappa and Jasim A Basheer