最初に
コミット権限をもらったRubyライブラリが古く、
sum
のない時代に書かれたライブラリでinject(:+)
で総和が求められていました。1
a = [1, 2, 3]
p a.inject(:+) # => 6
このように要素が数値の配列であれば、総和を求めることができます。2
問題点
しかし、空の配列でinject(:+)
を使うと、0
ではなくnil
を返してきます。
a = []
p a.inject(:+) # => nil
inject
は総和を求めるのに限られたメソッドではないので、「要素に何もないから数値の総和を求めたいのだろう」と勝手に決めつけたりせず、:+
を使うことなく淡々とnil
を返してきます。
この挙動を知らずに書かれているコードであったため、そのライブラリはバグっていました。
解決策
空の配列に対する総和を0
にしたければ、単純にsum
に置き換えると良いでしょう。
a = []
p a.sum # => 0
なお、小数計算ではinject
よりもsum
の方が誤差が少ないらしいです。
今のバージョンで、数値の総和を計算をするならsum
一択でしょう。
備考: もしも古いバージョンに対応するなら
メンテナンス期間の終わっている古いv2.4未満の話になりますが、
もしsum
の使えないバージョンに対応する必要があるなら、
「引数を2個にして、最初の引数を0
を指定」する方法があります。
a = []
p a.inject(0, :+) # => 0
簡単に言えば、最初の引数から演算します。
最初の引数は空配列のときに返される値になるだけでなく、
要素があれば第1引数も他の数と同じように最初に足されます。
詳しくは、Rubyリファレンスマニュアルを見たり、別途調べたください。
備考: 総和以外のケースでも
inject(:+)
という総和のケースではsum
に置き換えられますが、
その他の演算に関しては特別なメソッドは用意されていないです。
その他の場合でも、引数は2個とる必要がないか検討した方が良いでしょう。
例えば、要素の全ての積をとる総乗のケースです。
p [2, 4].inject(1, :*) # => 8 (= 1 x 2 x 4)
p [].inject(1, :*) # => 1 (= 1)
総乗のケースでは、最初の引数を1
にします。
もし、最初の引数を0
にすると0
になにかけても0
なので、
返り値が必ず0
になってしまいます……。
p [2, 3].inject(0, :*) # => 0 (= 0 * 2 * 3)
最後に
本当に言いたい大事なこと
総和を求めるだけに限らない話ですが、要素が0
のような何もないケースは一般的でない処理で、意識してないと試し忘れたり、テストを書かなかったりするかもしれません。
しかし、0
のようなケースこそ、エッジケースなのでバグりやすいです。
要素数が0個、1個、複数個のパターンで、テストは書くなり試した方が良いでしょう。