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, 3, 4] => 10
{a: 1, b: 6, c: -3} => 4
Active Support already implements Enumerable#sum
> [1, 2, 3, 4].sum
#=> 10
> {a: 1, b: 6, c: -3}.sum{ |k, v| v**2 }
#=> 46
> ['foo', 'bar'].sum # concatenation of strings
#=> "foobar"
> [[1], ['abc'], [6, 'qwe']].sum # concatenation of arrays
#=> [1, "abc", 6, "qwe"]
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, 3, 4].sum
#=> 10
> {a: 1, b: 6, c: -3}.sum { |k, v| v**2 }
#=> 46
> ['foo', 'bar'].sum
#=> TypeError: String can't be coerced into Integer
> [[1], ['abc'], [6, 'qwe']].sum
#=> TypeError: Array can't be coerced into Integer
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.
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.
> ['foo', 'bar'].sum('')
#=> "foobar"
> [[1], ['abc'], [6, 'qwe']].sum([])
#=> [1, "abc", 6, "qwe"]
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.