Ruby 2.4 implements Enumerable#sum

Mohit Natoo

Mohit Natoo

November 2, 2016

This blog is part of our  Ruby 2.4 series.

It is a common use case to calculate sum of the elements of an array or values from a hash.

1
2[1, 2, 3, 4] => 10
3
4{a: 1, b: 6, c: -3} => 4
5

Active Support already implements Enumerable#sum

1
2> [1, 2, 3, 4].sum
3 #=> 10
4
5> {a: 1, b: 6, c: -3}.sum{ |k, v| v**2 }
6 #=> 46
7
8> ['foo', 'bar'].sum # concatenation of strings
9 #=> "foobar"
10
11> [[1], ['abc'], [6, 'qwe']].sum # concatenation of arrays
12 #=> [1, "abc", 6, "qwe"]
13

Until Ruby 2.3, we had to use Active Support to use Enumerable#sum method or we could use #inject which is used by Active Support under the hood.

Ruby 2.4.0 implements Enumerable#sum as part of the language itself.

Let's take a look at how sum method fares on some of the enumerable objects in Ruby 2.4.

1
2> [1, 2, 3, 4].sum
3 #=> 10
4
5> {a: 1, b: 6, c: -3}.sum { |k, v| v**2 }
6 #=> 46
7
8> ['foo', 'bar'].sum
9 #=> TypeError: String can't be coerced into Integer
10
11> [[1], ['abc'], [6, 'qwe']].sum
12 #=> TypeError: Array can't be coerced into Integer
13

As we can see, the behavior of Enumerable#sum from Ruby 2.4 is same as that of Active Support in case of numbers but not the same in case of string or array concatenation. Let's see what is the difference and how we can make it work in Ruby 2.4 as well.

Understanding addition/concatenation identity

The Enumerable#sum method takes an optional argument which acts as an accumulator. Both Active Support and Ruby 2.4 accept this argument.

When identity argument is not passed, 0 is used as default accumulator in Ruby 2.4 whereas Active Support uses nil as default accumulator.

Hence in the cases of string and array concatenation, the error occurred in Ruby because the code attempts to add a string and array respectively to 0.

To overcome this, we need to pass proper addition/concatenation identity as an argument to the sum method.

The addition/concatenation identity of an object can be defined as the value with which calling + operation on an object returns the same object.

1
2> ['foo', 'bar'].sum('')
3 #=> "foobar"
4
5> [[1], ['abc'], [6, 'qwe']].sum([])
6 #=> [1, "abc", 6, "qwe"]
7

What about Rails ?

As we have seen earlier, Ruby 2.4 implements Enumerable#sum favouring numeric operations whereas also supporting non-numeric callers with the identity element. This behavior is not entirely same as that of Active Support. But still Active Support can make use of the native sum method whenever possible. There is already a pull request open which uses Enumerable#sum from Ruby whenever possible. This will help gain some performance boost as the Ruby's method is implemented natively in C whereas that in Active Support is implemented in Ruby.

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.