はじめに
転職をきっかけにRubyを触り始めており、プロダクションコードや書籍、チュートリアルでeach
とmap
が多用されいたのですが、メソッドの処理が似ているということもあり混乱してしまいました。
そこで、この二つのインスタンスメソッドの共通点や異なる点をリファレンスマニュアルを参考にしてまとめてみました。
eachとmapの共通点
共通点は以下の二つです。
- Arrayクラスのインスタンスメソッドである
- ブロックを省略した場合は
Enumerator
が返る
Arrayのリファレンスマニュアルのインスタンスメソッドを確認すると、each
とmap
があるのでどちらもArrayクラスのインスタンスメソッドであることがわかります。
また、irb(interactive Ruby)
でブロックを省略した挙動を確認してみるとEnumerator
というものが返ってくることがわかります。
irb(main):001> [1, 2, 3].each
=> #<Enumerator: ...>
irb(main):002> [1, 2, 3].map
=> #<Enumerator: ...>
Enumeratorって何?
リファレンスには
each 以外のメソッドにも Enumerable の機能を提供するためのラッパークラスです。また、外部イテレータとしても使えます。
とあります。
Enumerable
は繰り返しを行うためのモジュールで、このモジュールをインクルードすることでEnumerable
に定義されたインスタンスメソッドが使えるようになります。
Enumerable
のリファレンスでは以下のように記述されています。
繰り返しを行なうクラスのための Mix-in。このモジュールのメソッドは全て each を用いて定義されているので、インクルードするクラスには each が定義されていなければなりません。
実際にEnumerable
をインクルードしてみて確認します。
irb(main):035* class MyClass
irb(main):036* include Enumerable # <= Enumerableをインクルード
irb(main):037*
irb(main):038* def each; end
irb(main):039> end
=> :each
irb(main):040> MyClass.instance_methods # <= インスタンスメソッドを確認
=>
[:each,
:take_while,
:drop,
:drop_while,
:cycle,
:chunk,
:slice_before,
:slice_after,
:slice_when,
...]
# ↑ Enumerableで定義されているインスタンスメソッドが列挙される
まとめると、each
,map
でブロックを省略した場合には、Enumerable
という繰り返しを行うモジュールのラッパークラスであるEnumerator
クラスが返されるということですね。
また、Enumerable
リファレンスの続きは以下のように記述されており、map
もArrayクラスでオーバーライドされているメソッドの一つです。
Array, Hash, Range, Enumerator等のクラスで、 Enumerableモジュールはインクルードされています。ただし、効率化のため、そのクラスでEnumerableと同名・同等の機能を再定義(オーバーライド)しているケースも少なくなく、特にArrayクラスでは同名のメソッドを再定義していることが多いです。
eachとmapの相違点
相違点は以下の二つです。
- 処理内容
- 戻り値(ブロックが与えられた場合)
リファレンスを比較して確認してみます。
each
each -> {|item| ... } -> self
各要素に対してブロックを評価します。
each
はブロック内の処理を評価するのみで要素を加工した結果を返しません。self(レシーバ自身)
を返します。
[1, 2, 3, 4, 5].each { |i| i**2 } # <= ブロック内で要素の冪乗を返している
=> [1, 2, 3, 4, 5] # <= 結果は変わらない
# object_idを確認してみる
arr = [1, 2, 3, 4, 5]
arr.object_id
=> 788540
new_arr = arr.each { |i| i**2 }
new_arr.object_id
=> 788540 # <= arrと同じ
map
map -> {|item| ... } -> [object]
各要素に対してブロックを評価した結果を全て含む配列を返します。
map
はブロック内の戻り値で新しい配列を作成して返します。
[1, 2, 3, 4, 5].map { |i| i**2 } # <= ブロック内で要素の冪乗を返している
=> [1, 4, 9, 16, 25]
# object_idを確認してみる
arr = [1, 2, 3, 4, 5]
arr.object_id
=> 788540
new_arr = arr.map { |i| i**2 }
new_arr.object_id
=> 993360 # <= arrと異なる