Duck typing in Ruby: thoughts on checking argument types.

It doesn’t take much time in the Ruby world to hear the term duck typing. It wasn’t until I was writing a method for a recent project that was to accept either one or many ids that I actually stopped and actually investigated it. I knew what most people know about it: “If it walks like a duck and quacks like a duck, then it’s a duck” and that in actual practice this means you call call respond_to? to see if a method exists.

But as I was working on the code I found I kept wanting to type object.kind_of?(Array) but since this seemed to clash with the idea of duck typing I thought it was time to investigate a little bit.

The reason the type of an object using #kind_of? is not that useful is that Ruby supports multiple inheritance via the inclusion of modules. Since an object’s methods may have come from either a parent class or an included module, knowing the objects class doesn’t provide much information. If Tiger.new.kind_of?(Felidae) just returned true, is it safe to call the #domesticated? method? Calling #kind_of? just doesn’t help you make that decision.

module Wild
  def domesticated?
    false
  end
end

class Felidae
  def claws?
    true
  end
end

class Tiger < Felidae
  include Wild
end

It seems that there are two schools of thought on duck typing. First, “soft” duck typing. Paraphrasing an example from Dave Thomas’ pickaxe book shows the standard approach:

def some_method param
  if param.respond_to? :<<
    # must be something we can write to!
    param << "a string to be added"
  end
end

The difficulty here is that respond_to? famously fails when you are using method_missing to catch and respond to method calls. The only way to be sure that respond_to? is not deceiving you is to go ahead and call the method. It is this approach that people call “hard” duck typing:

def some_method param
  param << "a string to be added"
end

As Dave explains:

“You don’t need to check the type of the arguments. If they support << (in the case of result) or title and artist (in the case of song), everything will just work. If they don’t, your method will throw an exception anyway (just as it would have done if you’d checked the types). But without the check, your method is suddenly a lot more flexible: you could pass it an array, a string, a file, or any other object that appends using <<, and it would just work.”
— Programming Ruby second edition pg 355 – Dave Thomas

The interesting thing to me here, is the rationale for just calling the method instead of doing something like this:

def some_method param
  begin
    param << "a string to be added"
  rescue NoMethodError => e
    raise ArgumentError, "Needs to support the << method"
  end
end

Constantly wrapping method calls in begin/rescue/end is pretty heinous from any angle; aesthetics, readability or performance. While hard duck-typing might come off as a little cavalier, it’s pretty much assumed in the Ruby community you have a test suite to make sure none of these NoMethod exceptions end up in production.

Another interesting take on the issue is the approach taken by Jeremy Kemper, the top commiter to the Rails framework. With commit 609c1988d2e274b he added the #acts_like? method to Object:

class Object
  # A duck-type assistant method. For example, Active Support extends Date
  # to define an acts_like_date? method, and extends Time to define
  # acts_like_time?. As a result, we can do "x.acts_like?(:time)" and
  # "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that
  # we want to act like Time simply need to define an acts_like_time? method.
  def acts_like?(duck)
    respond_to? :"acts_like_#{duck}?"
  end
end

and then to each of the core classes in the standard library he added an acts_like_x? method. Here is what the one for Date looks like:

class Date
  # Duck-types as a Date-like class. See Object#acts_like?.
  def acts_like_date?
    true
  end
end

These were added to ActiveSupport back in 2009 but for some reason they don’t seem to be widely used in the codebase. This surprises me because this approach seems to avoid NoMethodErrors without cluttering your code with rescue statements. On top of that it has none of the problems inherent in respond_to?.

I hope that helps people out a little. I know it has helped clarify my thoughts. Just one more reason to go and read the source.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s