webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

コレクションの取り扱いあれこれ

Effective Rubyの勉強メモです。

【目次】

コレクションをメソッドに渡すときの注意

rubyでは、コレクション(例えば配列)をメソッドの引数に渡したとき、メソッド内ではどのように配列を参照しているのでしょうか?

実はrubyではメソッドに渡された配列は参照渡しされます。

参照渡しとは、オブジェクトそのものではなく、オブジェクトへの参照を渡すことです。オブジェクトはメモリ上のどこかに格納されており、そのメモリ上のどこを参照しているかのアドレスをメソッドに渡します。

すなわち

a = [1, 2, 3]
def address(object)
  object.object_id
end

p a.object_id
p address(a)

スポンサーリンク

上記のコードの二つのpメソッドは同じ値を返します。

なので、もしメソッド内で引数の配列に変更を加えた場合は渡した配列も変更されるため注意が必要です。以下は配列から数値が含まれる文字列を削除して返すメソッドです。

def str_only(array)
  array.delete_if {|a| !a.to_s.match(/[0-9]/)}
  array
end

もちろん、引数に渡した値は参照するのみで、変更するのはあまりいいとは思えません。

破壊的メソッドを使わない対処法

そこで、破壊的メソッドであるdelete_ifをrejectメソッドで書き換えます

def str_only(array)
  array.reject{|a| !a.to_s.match(/[0-9]/)}
end

rejectメソッドはレシーバを破壊的に書き換えず、新しく作った配列を返します。

コレクションをコピーしておく

しかし、あちこちのメソッドで破壊的に書き換えないという制限を頭にいれるよりも、もっといい方法があります。

それがコピーを残しておく方法です。

rubyにはオブジェクトのコピーを作る方法として、cloneとdupの2種類あります。

cloneはまさにクローンを作るメソッドで、配列の中身、フリーズの状態、特異メソッドもコピーして新しく配列を作ります。

一方dupは重複を意味し、同じ内容の新しいオブジェクトを作ります。内容が同じだけで、フリーズの状態、特異メソッドはコピーしません。

今回はメソッド内で変更される場合もあるのでdupを使ったほうがいいでしょう。

僕も業務ではdupしか使いません。

a = ["apple", "banana", "1emon"]
def str_only(array)
  array.delete_if {|a| !a.to_s.match(/[0-9]/)}
  array
end

str_only(a.dup)

ディープコピーする

しかし、dup,cloneにも弱点があります。それは、浅いコピーしかとらないことです。

すなわち、配列としては新しいオブジェクトになっていますが、配列の各要素はもとの配列と同じオブジェクトを参照しているのです。

a = ["apple", "banana"]
b = a.dup
b.each(&:upcase!)
b #=> ["APPLE", "BANANA"]
a #=> ["APPLE", "BANANA"]

そこで、標準ライブラリであるMarshalモジュールを使うことで深いコピーをすることができます。

Marshalモジュールはrubyオブジェクトをファイルに書き出したり、読み込んだりする機能を提供しているモジュールです。

a = ["apple", "banana"]
b = Marshal.load(Marshal.dump(a))
b.each(&:upcase!)
b #=> ["APPLE", "BANANA"]
a #=> ["apple", "banana"]

dumpメソッドの第一引数に配列を入れることで新しくメモリ上にダンプしておきます。どこまで深く潜ってオブジェクトを保存するかは第2引数で指定できますが、ない場合は無制限に潜っていきます。

ただし、リファレンスにも書いてありますが、Marshalではダンプできないクラス(ProcやThreadなど)もあり、その場合は例外が発生してしまいます。

さらに、特異メソッドを定義したオブジェクトもdumpするときに例外が発生します。普通の文字列とかだと問題ないですが、Marshalを使う場合は注意が必要です。

Effective Ruby

Effective Ruby

Ruby on Rails 5アプリケーションプログラミング

Ruby on Rails 5アプリケーションプログラミング