要約
コレクションに対して
- 絞り込みと
- データ処理
をしたいときはeach_with_objectを使うと良いという話です。
詳細
Effective Rubyを読む中で、よく書きがちな以下のコードに対してinject / reduceを用いよという教えが書かれていました。
# こんなデータがあるとする。
User = Struct.new(:name, :age)
users = [
User.new('user1', 21),
User.new('user2', 19),
User.new('user3', 23),
User.new('user4', 35),
User.new('user5', 10),
User.new('user6', 18),
User.new('user7', 24),
User.new('user8', 15),
User.new('user9', 21),
]
# 成人だけを絞り込んで、その名前リストが欲しい。そんなときは...
# これよりも
users.select { |u| u.age >= 20 }.map(&:name)
# これがいい(パフォーマンス的に。)
users.inject([]) do |result, user|
result << user.name if user.age > 20
result # injectのブロックは必ずnil以外を返そう。
end
「お、なるほど」「injectの引数に空配列か〜」と勉強になったなと思いながら、そのようなコードを書いたところ、rubocopが**Use `each_with_object` instead of `inject`.
**とメッセージを発していました。
何だこれと思い調べてみると、リファレンスには以下のように説明が。
each_with_indexメソッドは、要素を使って何らかのオブジェクトを操作するのに使います。要素の数だけブロックを繰り返し実行し、繰り返しごとにブロック引数itemには各要素を、memoには引数objectで指定したオブジェクトを入れます。戻り値は、objectのオブジェクトです。
- each_with_objectの引数をブロック引数に持ち
- その引数をメモとして更新しつつ、各要素ごとにブロックを回す
- なので、ブロックの返り値を明示的に書かなくていい
- 返り値を次のブロック引数として渡すわけではないので
といった使い方で、select + map目的であればeach_with_objectを使ったほうが用途に適切なコードになるとのこと。
書き換え後がこちら
users.each_with_object([]) do |user, result|
result << user.name if user.age > 20
end
冒頭の目的であればeach_with_objectを使うべきというtipsでした。