ActiveSupportのArray#sumは七変化する

公開:2021/01/15
更新:2021/01/15
1 min読了の目安(約1400字TECH技術記事

Railsエンジニアなら必ずお世話になるActiveSupportはRubyデフォルトの Array#sumを拡張する処理が含まれている。
とあるライブラリ開発でActiveSupportの実装を追いかけた時に見つけた #sumの不思議な挙動をまとめました

例によってQiitaに書いたやつをこっちにも貼り付けてみてます。

[1,2,3].sum

おそらくは1番標準的な使い方。

[5, 15, 10].sum                                                                       
=> 3

ちなみにこの使い方はActiveSupportを使わなくても使用できる。
昔はRuby標準の Array#sumよりActiveSupportの Array#sumのほうが高速に動作したがある時期を持って本家に取り込まれたそうで今はどっちも同じ早さという話

['foo', 'bar'].sum

ActiveSupport無しだとエラーになる↓


pry(main)> ['foo', 'bar'].sum
TypeError: String can't be coerced into Integer
from (pry):1:in `+'

ところがActiveSupportありだとこうなる

pry(main)> ['foo', 'bar'].sum
=> "foobar"

唐突に +Array.joinに化けた。
そうは言ってもRubyで文字列を + すると連結できるのでこの挙動自体が理解できないわけではない。

[[1,2,3], [4,5]].sum

先の例同様にActiveSupport無しだとエラーになる

pry(main)> [[1,2,3], [4,5]].sum
TypeError: Array can't be coerced into Integer
from (pry):2:in `+'

じゃあActiveSupportありだとどうなるのか?
こうなります

pry(main)> [[1,2,3], [4,5]].sum
=> [1, 2, 3, 4, 5]

もしかしたら Array.flatten.sumと同じ挙動になると予想された人もいたのではないでしょうか?
残念なことに結果は sumされません。代わりに flattenされます。なんで...?

おまけ

ArrayじゃなくHashでsumしたらどうなるだろうか?

pry(main)> {a: 10, b: 20}.sum
=> [:a, 10, :b, 20]

!?!?!?
ソースコードを見てると以下の記述を発見

  # We can't use Refinements here because Refinements with Module which will be prepended
  # doesn't work well https://bugs.ruby-lang.org/issues/13446

なるほどぉ...