Making Ruby Quack—Why We Love Duck Typing

Dan Cheail
Share

One of the most lauded features of Ruby is its support for the technique known as duck typing. Despite the humorous name, duck typing enables Ruby developers to write succinct, clean, and readable code with very little effort—all the things that attracted us to Ruby in the first place.

“What would Duck Dodgers do?”

The basic premise of duck typing is simple. If an entity looks, walks, and quacks like a duck, for all intents and purposes it’s fair to assume that one is dealing with a member of the species anas platyrhynchos. In practical Ruby terms, this means that it’s possible to try calling any method on any object, regardless of its class.

To demonstrate, here’s a simple scenario: you’re working with a system that tracks shipping manifests for each container on a ship. Data for each manifest is easily obtained through a RESTful web service:

# Fields:
# * package_count: number of packages in manifest (integer)
# * mass:          total mass of all packages, in kilograms (BigDecimal)
class ShippingManifest
  attr_accessor :package_count, :mass
end

Each ShippingManifest tracks the number of packages, as well as the combined mass for all the packages. Your task is to take a set of manifests (provided in an Array) and give a summation of the total mass and the total number of packages in the set.

“You say the Loch Ness Monster is living in your jacuzzi?”

The problem looks simple enough, right? We could probably bash it out in nine or ten lines of code:

def summarise_manifests
  # Grab the latest set of Manifests (returns an Array)
  manifest_set = ShippingManifest.all

  # Initialize our counters
  total_package_count = 0
  total_mass          = BigDecimal("0")

  manifest_set.each do |m|
    total_package_count += m.package_count
    total_mass += m.mass
  end

  return [total_package_count, total_mass]
end

Easy, but verbose. It’s possible to reduce this method to a single line of code:

def summarise_manifests
  ShippingManifest.all.sum
end

How? Duck typing!

“Skeleton key, eh? Where did you pick that up?”

Our one-line solution takes advantage of one of Ruby’s most powerful features: the Enumerable module. Enumerable provides an API to easily manipulate collections of objects—most commonly Array and Hash.

Specifically, we’re using a method defined in ActiveSupport: sum, which is available in every Rails project. As the name suggests, it’s used to calculate the sum of a set of objects. It does this by using inject, a function that uses a block to reduce a set of objects to a single value. In the case of sum, it does this by using the addition operator.

This makes perfect sense when we’re talking about numbers: 1 + 1 = 2 is fundamental arithmetic. And it’s also easy to see how it can be used to concatenate strings together:

['foo', 'bar', 'baz'].sum # => 'foobarbaz'

But how does Ruby know how to add two instances of our ShippingManifest class?

Easy. We tell it how.

“That’s only part of who I am. I’m actually quite complex.”

“Everything is an object” is one of Ruby’s core mantras. The implication of this is that (nearly) every operator is actually a method call on an instance of an object—which grants us the ability to define our own implementation for that operator. It means we can do this:

class ShippingManifest < ActiveResource::Base
  # Returns a new instance of ShippingManifest containing the
  # sum of both `mass` and `package_count`
  def +(other)
    self.dup.tap do |neu|
      neu.mass          = @mass + other.mass
      neu.package_count = @package_count + other.package_count
    end
  end
end

By adding a few simple lines of code, we’ve added functionality that has made the potential ShippingManifest much more usable by the wider Ruby library.

“And let me remind you again, folks, that you’re listening to Truth or—Aaaugh!”

Implementing custom behavior for the + operator only scratches the surface of what’s possible through duck typing. There are other operators that have custom behavior implemented: &, *, -, Even == can have custom behavior. Need to sort a set of records? Implement the comparison operator () in your classes and you’ll be able to take full advantage of the sorting methods provided in Enumerable.

As always, some example code for this post is available on our GitHub account—feel free to fork it and experiment! For starters, try implementing behaviour for - and , then experiment with the methods in Enumerable.

If you have queries about any other aspect of Ruby, feel free to ask in the comments!

CSS Master, 3rd Edition