inject vs eachwithobject

… and check why 5600+ Rails engineers read also this

inject vs each_with_object

Recently I’ve been adding new exercises to DevMemo.io and some of them were about Enumerable#inject (also available as aliased method reduce) and some of them were about Enumberable#each_with_object. And I’ve been thinking about a small guideline for when to use which.

inject

  • better for operations on mutable objects/collections which return a new value
  • good for immutable primitives and value objects which return a new value when changed

each_with_object

  • better for mutable operations on objects/containers
    • especially such as Hash, Array
  • it makes sense to use it if you provide a fresh object as a starting point and build it
    • not that useful if you want to modify an existing object

Example 1

Let’s see the differences with some examples. Imagine you have a collection of objects and you want to build a new Hash using them and perform some kind of mapping.

  • You are building a new object (Hash lower_to_upper)
  • There is a fresh, starting point {}

In such case, each_with_object is very convenient.

lower = 'a'..'z'
lower_to_upper = lower.each_with_object({}) do |char, hash|
  hash[char] = char.upcase
end

On the other hand inject is less convenient:

lower = 'a'..'z'
lower_to_upper = lower.inject({}) do |hash, char|
  hash[char] = char.upcase
  hash
end

because inject requires that the memoized value provided for subsequent block calls (hash which initially is {}) is returned by previous block calls. So even though you constantly operate on the same object, you always need to return it in the last line of the provided block.

each_with_object on the other hand always calls the block with the same initial object that was passed first as first argument to the method.

Example 2

But let’s say you already have an existing object that you would like to modify. In such case, it would be usually preferable to just use each over each_with_object but each_with_object can be a bit shorter if you still need to return the changed object.

All three versions below generate the same result.

mapping = {'ż' => 'Ż', 'ó' => 'Ó'}
lower = 'a'..'z'
lower.each do |char|
  mapping[char] = char.upcase
end
return mapping # optionally
mapping = {'ż' => 'Ż', 'ó' => 'Ó'}
lower = 'a'..'z'
lower.each_with_object(mapping) do |char, hash|
  hash[char] = char.upcase
end
mapping = {'ż' => 'Ż', 'ó' => 'Ó'}
lower = 'a'..'z'
lower.each_with_object(mapping) do |char|
  mapping[char] = char.upcase
end

I would say that each is preferable if you mutate an existing collection, because usually you don’t need to return it. After all, whoever gave you that object as an argument, wherever it comes from, that place in code probably still has reference to this object.

Example 3

This time you are not mutating internal state of an object but rather always creating a new one. The operation that you use always returns a new object.

The most simple example can be + operator for numbers.

a = 1
b = 2

a.frozen?
# => true
b.frozen?
# => true

c = a + b
# => 3

There is no way to change the Integer object referenced by variable a into 3. The only thing you can do is assign a different object to variable a or b or c.

It’s an obvious example. But Date is a less obvious one.

require 'date'
d = Date.new(2017, 10, 10)

If you want a different date, you cannot change the existing Date instance. You need to create a new one.

d.day=12
# => NoMethodError: undefined method `day=' for #<Date:

e = Date.new(2017, 10, 12)

So that was a small introduction. What does it have to do with inject. If your initial object is immutable, inject is the way to go.

(5..10).inject(:+)
(5..10).inject(0, :+)
(5..10).inject{|sum, n| sum + n }

(5..10).inject(1, :*)

or

starting_date = Date.new(2017,10,1)
result = [1, 10].inject(starting_date) do |date, delay|
  date + delay
end
# => Date.new(2017,10,12)

or

# gem install money
require 'money'
[
  Money.new(100_00, "USD"),
  Money.new( 10_00, "USD"),
  Money.new(  1_00, "USD"),
].inject(:+)
# => #<Money fractional:11100 currency:USD>

Example 4

This time we will be creating a new object every time but not because we can’t change the internal state. This time it’s because a certain method returns a new object.

result = [
 {1 => 2},
 {2 => 3},
 {3 => 4},
 {1 => 5},
].inject(:merge)
# => {1=>5, 2=>3, 3=>4}

Hash#merge merges two hashes and returns a new one. That’s why we can use it easily with inject.

Compare it with

[
 {1 => 2},
 {2 => 3},
 {3 => 4},
 {1 => 5},
].each_with_object({}) {|element, hash| hash.merge!(element) }
# => {1=>5, 2=>3, 3=>4}

Sidenote

Isn’t it a bit irritating that the order of arguments passed to the block for inject and each_with_object is reversed?

lower_to_upper = lower.each_with_object({}) do |char, hash|
  hash[char] = char.upcase
end

lower_to_upper = lower.inject({}) do |hash, char|
  hash[char] = char.upcase
  hash
end

Learn more

Soon we will be launching Ruby’s Enumerable course on DevMemo.io. Try it and subscribe if you like it.

You might also like