Home > Articles > Programming > Ruby

Eloquent Ruby: Embrace Dynamic Typing

  • Print
  • + Share This
In this chapter from his book, Eloquent Ruby, Russ Olsen In looks at how dynamic typing allows you to build programs that are simultaneously compact, flexible, and readable. Unfortunately, nothing comes for free, so he also looks at the downsides of dynamic typing and at how the wise Ruby programmer works hard to make sure the good outweighs the bad.
This chapter is from the book

This chapter is from the book

How? Why? These are the two questions that every new Ruby coder—or at least those emigrating from the more traditional programming languages—eventually gets around to asking. How can you possibly write reliable programs without some kind of static type checking? And why? Why would you even want to try? Figure out the answer to those two questions and you're on your way to becoming a seasoned Ruby programmer. In this chapter we will look at how dynamic typing allows you to build programs that are simultaneously compact, flexible, and readable. Unfortunately, nothing comes for free, so we will also look at the downsides of dynamic typing and at how the wise Ruby programmer works hard to make sure the good outweighs the bad.

This is a lot for one chapter, so let's get started.

Shorter Programs, But Not the Way You Think

One of the oft-repeated advantages of dynamic typing is that it allows you to write more compact code. For example, our Document class would certainly be longer if we needed to state—and possibly repeat here and there—that @author, @title, and @content are all strings and that the words method returns an array. What is not quite so obvious is that the simple "every declaration you leave out is one bit less code" is just the down payment on the code you save with dynamic typing. Much more significant savings comes from the classes, modules, and methods that you never write at all.

To see what I mean, let's imagine that one of your users has a large number of documents stored in files. This user would like to have a class that looks just like a Document,1 but that will delay reading the contents of the file until the last possible moment: In short, the user wants a lazy document. You think about this new requirement for a bit and come up with the following: First you build an abstract class that will serve as the superclass for both the regular and lazy flavors of documents:

class BaseDocument

  def title
    raise "Not Implemented"
  end

  def title=
    raise "Not Implemented"
  end

  def author
    raise "Not Implemented"
  end

  def author=
    raise "Not Implemented"
  end

  def content
    raise "Not Implemented"
  end

  # And so on for the content=
  # words and word_count methods...

end

Then you recast Document as a subclass of BaseDocument:

class Document < BaseDocument
  attr_accessor :title, :author, :content

  def initialize( title, author, content )
    @title = title
    @author = author
    @content = content
  end

  def words
    @content.split
  end

  def word_count
    words.size
  end
end

Finally, you write the LazyDocument class, which is also a subclass of BaseDocument:

class LazyDocument < BaseDocument

  attr_writer :title, :author, :content

  def initialize( path )
    @path = path
    @document_read = false
  end

  def read_document
    return if @document_read
    File.open( @path ) do | f |
      @title = f.readline.chomp
      @author = f.readline.chomp
      @content = f.read
    end
    @document_read = true
  end

  def title
    read_document
    @title
  end

  def title=( new_title )
    read_document
    @title = new_title
  end

  # And so on...
end

The LazyDocument class is a typical example of the "leave it to the last minute" technique: It looks like a regular document but doesn't really read anything from the file until it absolutely has to. To keep things simple, LazyDocument just assumes that its file will contain the title and author of the document on the first couple of lines, followed by the actual text of the document.

With the classes above, you can now do nice, polymorphic things with instances of Document and LazyDocument. For example, if you have a reference to one or the other kind of document and are not sure which:

doc = get_some_kind_of_document

You can still call all of the usual document methods:

puts "Title: #{doc.title}"
puts "Author: #{doc.author}"
puts "Content: #{doc.content}"

In a technical sense, this combination of BaseDocument, Document, and LazyDocument do work. They fail, however, as good Ruby coding. The problem isn't with the LazyDocument class or the Document class. The problem lies with BaseDocument: It does nothing. Even worse, BaseDocument takes more than 30 lines to do nothing. BaseDocument only exists as a misguided effort to provide a common interface for the various flavors of documents. The effort is misguided because Ruby does not judge an object by its class hierarchy.

Take another look at the last code example: Nowhere do we say that the variable doc needs to be of any particular class. Instead of looking at an object's type to decide whether it is the correct object, Ruby simply assumes that if an object has the right methods, then it is the right kind of object. This philosophy, sometimes called duck typing,2 means that you can completely dispense with the BaseDocument class and redo the two document classes as a couple of completely independent propositions:

class Document
  # Body of the class unchanged...
end

class LazyDocument
  # Body of the class unchanged...
end

Any code that used the old related versions of Document and LazyDocument will still work with the new unrelated classes. After all, both classes support the same set of methods and that's what counts.

There are two lessons you can take away from our BaseDocument excursion. The first is that the real compactness payoff of dynamic typing comes not from leaving out a few int and string declarations; it comes instead from all of the BaseDocument style abstract classes that you never write, from the interfaces that you never create, from the casts and derived types that are simply irrelevant. The second lesson is that the payoff is not automatic. If you continue to write static type style base classes, your code will continue to be much bulkier than it might be.

  • + Share This
  • 🔖 Save To Your Account