Avoiding Inject

ruby

Mon Apr 07 13:30:00 -0700 2008

I was rather taken with inject when I first started getting serious with Ruby. It allows you to turn ugly, imperative-style loops requiring temporary variables into functional-style expressions. Like this:

total = 0
items.each do |item|
  total += item.price * item.quantity
end
total

Becomes:

items.inject(0) do |total, item|
  total += item.price * item.quantity
end

Much better. (Jay Fields expands on this, in case you’re not already an inject junkie.)

And yet, somehow, this hasn’t always sat quite right with me. I had to look up the syntax quite a bit when I was first learning it (I always wanted to do |item, total| rather than the reverse), and from time to time my use of inject seems to create subtle bugs that take me a while to figure out. It feels right in theory, but in practice something is a little off.

My partner and coding buddy Orion pointed out to me recently that map is actually a simpler solution in most cases. The trick is knowing the right Enumerable methods to go with it. Like sum, perfect for the example above:

items.map { |item| item.price * item.quantity }.sum

This breaks the process into two steps: extracting the information you want, then operating on the result set. (I daresay this might be a simple version of map/reduce.) Plus, it works elegantly for hashes, something that always frustrates me about inject. For example, turning a hash into a key-value string with inject is:

hash.inject("") do |string, key_value|
        string += "#{key_value[0]}=#{key_value[1]}\n"
end

With map, this is:

params.map { |key, value| "#{key}=#{value}" }.join("\n")

The two-step process wins out on elegance here, too.