🐕

Array#to_a と Array#entries の違い

に公開

Array#to_aArray#entries で軽微な違いがあると聞いたので調べてみました。

Enumerable#to_aEnumerable#entries

その前に Enumerable の話をします。
Enumerable では #to_a#entries の2つのメソッドが定義されています。
これらはいずれも #each から配列を生成するような挙動になっています。

class X
  include Enumerable

  def each
    yield("homu")
    yield("mami")
    yield("mado")
  end
end

x = X.new

# x.each の結果を元にして配列を返す
pp x.to_a    # => ["homu", "mami", "mado"]
pp x.entries # => ["homu", "mami", "mado"]

Array#to_aArray#entries

Enumerableinclude している Array でも Enumerable#to_a / entries は利用できるんですが『 #to_aArray クラスで再定義されている』という違いがあります。

# #to_a は Array で再定義されている
pp Array.instance_method(:to_a)
# => #<UnboundMethod: Array#to_a()>

# #entries は Enumerable で定義されている
pp Array.instance_method(:entries)
# => #<UnboundMethod: Enumerable#entries(*)>

Array#to_a の挙動がどう変わっているのかというと『 self の値をそのまま返す』という挙動になっています。
なので

  • Array#to_a => レシーバと同じオブジェクトを返す
  • Array#entries => レシーバと異なるオブジェクトを返す

という大きな違いがあります。

ary = [1, 2, 3]

pp ary.__id__ # => 16

# これは ary と同じオブジェクトを返す
pp ary.to_a.__id__ # => 16

# これは ary とは異なるオブジェクトを返す
pp ary.entries.__id__ # => 24

これによりどういうことが起きる?

Array#to_a の結果に対しては破壊的変更を行うと元々のオブジェクトまで破壊されてしまいます。

ary = %w(mado homu mami)

# #entries の場合は元の順番が維持される
ary.entries.sort!
pp ary
# => ["mado", "homu", "mami"]

# #to_a では ary の並び順まで変わってしまう
ary.to_a.sort!
pp ary
# => ["homu", "mado", "mami"]

もし、メソッドの引数に対して to_a.sort! のような破壊的変更を行った場合に元の引数の値まで変わってしまうので注意して使用する必要があります。

関連

GitHubで編集を提案

Discussion