Making Ruby Quack—Why We Love Duck Typing
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!