何でもかんでも 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
するのは、やり過ぎでした。