何でもかんでも deep_dup
ActiveSupport との違い。
- インスタンス変数も漏れなく
deep_dupします。 - Array の場合も自身を
dupして、レシーバが Array の子クラスなら、そのクラスのオブジェクトを返します。 - Hash のキーを
dupすることは考えていません。 - Struct にも対応しました。
module DeepDup
refine Object do
def deep_dup
return self if private_methods(false).include?(:dup)
dup.tap do |copy|
copy.instance_variables.each do |var|
val = copy.instance_variable_get(var)
copy.instance_variable_set(var, val.deep_dup)
end
end
end
end
refine Array do
def deep_dup
dup.tap { |ary| ary.map!(&:deep_dup) }
end
end
refine Hash do
def deep_dup
dup.tap do |hash|
hash.each { |key, val| hash[key] = val.deep_dup }
end
end
end
refine Struct do
def deep_dup
dup.tap do |struct|
struct.each_pair { |key, val| struct[key] = val.deep_dup }
end
end
end
end
考察
ActiveSupport は、どうしてインスタンス変数を deep_dup しないのだろう。
何でもかんでも dup するのはやり過ぎなので、dup が必要なインスタンス変数に限って、initialize_dup (dup の挙動だけ変更する。)や initialize_copy(dup と clone どちらの挙動も変更する。)で dup なり、deep_dup するようにして、オブジェクト本体の dup の挙動を変更するようにした方が、柔軟な設計が可能だからなのだろうか。
例としては、
class Foo
def initialize
@ary = []
end
def initialize_copy(orig)
@ary = orig.instance_variable_get(:@ary).deep_dup
end
end
こんな感じ。よくよく考えると、なんでもかんでも deep_dup すると、循環連結リストの処理がいつまでも終わらないことに気付いた。
拙い表現ではあるが、ActiveSupport では、インスタンス変数を持たない Array や Hash に関しては deep_dup すること自体が眼目であるのに対し、Object で deep_dup を定義しているのは、dup が禁止されているオブジェクトかどうかを見極めることが主要な目的だということなのだろうか。
結論
インスタンス変数を何でもかんでも deep_dup するのは、やり過ぎでした。