In the Wild
A good example of the Ruby typing philosophy of "if the method is there, it is the right object" is as close as your nearest file and string. Every Ruby programmer knows that if you want to open a Ruby file, you do something like this:3
open_file = File.open( '/etc/passwd' )
Sometimes, however, you would like to be able to read from a string in the same way that you read from a file, so we have StringIO:
require 'stringio' open_string = StringIO.new( "So say we all!\nSo say we all!\n" )
The idea is that you can use open_file and open_string interchangeably: Call readchar on either and you will get the next character, either from the file or the string. Call readline and you will get the next line. Calling open_file.seek(0) will put you back at the beginning of the file while open_string.seek(0) will put you at the beginning of the string.
Surprisingly, the File and StringIO classes are completely unrelated. The earliest common ancestor of these two classes is Object! Apparently reading and writing files and strings is different enough that the authors of StringIO (which was presumably written after File) decided that there was nothing to gain—in terms of implementation—from inheriting from File, so they simply went their own way. This is fairly typical of Ruby code, where subclassing is driven more from practical considerations—"Do I get any free implementation from inheriting from this class?"—than a requirement to make the types match up.
You can find another interesting example of the "don't artificially couple your classes together" thinking in the source code for the Set class, which we looked at briefly in Chapter 3. It turns out that you can initialize a Set instance with an array, like this:
five_even = [ 2, 4, 6, 8, 10 ] five_even_set = Set.new( five_even )
In older versions of Set, the code that inserted the initial values into the new Set instance looked like this:4
enum.is_a?(Enumerable) or raise ArgumentError, "not enumerable" enum.each { |o| add(o) }
These early versions of Set first checked to see if the variable enum, which held the initial members of the set, was an instance of Enumerable—arrays and many other Ruby collections are instances of Enumerable—and raised an exception if it wasn't. The trouble with this approach is that the requirement that enum be Enumerable is completely artificial. In the spirit of dynamic typing, all that Set should really care about is that enum has an each method that will iterate through all of the elements. Apparently the maintainers of the Set class agree, because the Enumerable check has disappeared from the current version of set.rb.