We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.
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.
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
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.