[ruby]浅いコピーと深いコピー

## about

shallow copy(浅いコピー)だdeep copy(深いコピー)だとよくわからないままrubyを使ってたらまんまと引っかかったので備忘録として書いてみる。間違っていたらご指摘ください:bow: 



## `=` による複製

浅い・深いの関係ないところとして、=によってオブジェクトを複製すると両者は名前が違うだけの、同じ中身を参照している関係になります。
なので、破壊的変更を加えると複製元も先も変わります。

```ruby

origin = "hoge"
=> "hoge"

copy = origin
=> "hoge"

origin.object_id
=> 70347211263140

copy.object_id
=> 70347211263140

orig.equal? copy
=> true

copy.gsub!("hoge", "fuga")
=> "fuga"

origin
=> "fuga"
```

## `dup` と `clone`

https://docs.ruby-lang.org/ja/latest/method/Object/i/clone.html

rubyにはオブジェクトの複製メソッドとして、`dup`と`clone`というメソッドが用意されています。

```ruby
origin = "hoge"
=> "hoge"

copy = origin.dup
=> "hoge"

origin.object_id
=> 70118270776180

copy.object_id
=> 70118270801780
```

上記のように、object_idが異なるので、別物として作られていることがわかります。
そのため、この変数に破壊的メソッドを実行してもお互いに影響を及ぼすことはありません。

```ruby
origin = "origin"
=> "origin"

cloned_origin = origin.clone
=> "origin"

cloned_origin.gsub!("origin", "clone")
=> "clone"

origin
=> "origin"

cloned_origin
=> "clone"
```

しかし、一つ気をつけなければならないのは、この2つのメソッドが生み出すコピーが「浅いコピー」であるという点です。

## 浅いコピーとは？
浅いコピー(shallow copy)とは、実体となる値の参照場所だけ複製して、値そのものは複製しないというコピー手法のことです。Rubyに限らず、JavaScriptといった他の言語でも採用している汎用的なパターンです。

例として、るりまにはこのように記載があります。

https://docs.ruby-lang.org/ja/latest/method/Object/i/clone.html

> clone や dup はオブジェクト自身を複製するだけで、オブジェクトの指している先(たとえば配列の要素など)までは複製しません。これを浅いコピー(shallow copy)といいます。


```ruby
# arrayの場合
origin_array = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]

cloned_origin_array = origin_array.dup
=> [1, 2, 3, 4, 5]

origin_array.object_id
=> 299540
cloned_origin_array.object_id
=> 330260

origin_array[0].object_id
=> 3
cloned_origin_array[0].object_id
=> 3

# hashの場合
origin_hash = {a: 1, b: 2, c: 3}
=> {:a=>1, :b=>2, :c=>3}
cloned_origin_hash = origin_hash.clone
=> {:a=>1, :b=>2, :c=>3}

origin_hash.object_id
=> 563260
cloned_origin_hash.object_id
=> 603040

origin_hash[:a].object_id
=> 3
cloned_origin_hash[:a].object_id
=> 3
```

このように、arrayやhashのオブジェクトは別になっていますが、その中身は同じobject_idになっていることがわかります。
なので、

```ruby

# dupメソッド
origin_arr = ["hoge", "fuga"]
=> ["hoge", "fuga"]

dup_arr = origin_arr.dup
=> ["hoge", "fuga"]

dup_arr.first.gsub!("hoge", "piyo")
=> "piyo"

dup_arr
=> ["piyo", "fuga"]

origin_arr
=> ["piyo", "fuga"]

# cloneメソッド
clone_arr = origin_arr.clone
=> ["piyo", "fuga"]

clone_arr.first.gsub!("piyo","hoge")
=> "hoge"

clone_arr
=> ["hoge", "fuga"]

origin_arr
=> ["hoge", "fuga"]
```

上記のように、dupやcloneメソッドを使ってarrayやhashを複製した場合、この中身に対して破壊的メソッドを実行すると元のオブジェクトの中身にも変更が及んでしまいます。危険だ。

## 深いコピーを実現するためには

人間生きていれば深いコピー、つまりオリジナルと分離した完全なる複製を生成したい時がある。そういうときにどうしたらいいか。以下の3つの方法があるようです。

### 1. `deep_dup` メソッドを使う(要 activesupport)

深いコピーを複製してくれるメソッドです。これは**rubyの標準ライブラリにはないメソッド**なので、`activesupport`をrequireしないと使えないことに留意してください。

```ruby

require 'active_support'
=> true

require 'active_support/core_ext'
=> true

deep_dup_arr = origin_arr.deep_dup
=> ["hoge", "fuga"]

deep_dup_arr.first.gsub!("hoge", "piyo")
=> "piyo"

origin_arr
=> ["hoge", "fuga"]

deep_dup_arr
=> ["piyo", "fuga"]
```


### 2.`initialize_copy` メソッドを定義して浅いコピーを2重に行う

例えば以下のようなクラスがあったとします。

```ruby

class Profile
  attr_accessor :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end
end
```

このクラスを呼び出すオブジェクトを定義し、浅いコピーで複製したら、中身は同じものになります。
そのため、複製したオブジェクトに対して破壊的メソッドを実行すると・・・

```ruby

taro = Profile.new('taro', 20)
=> #<Profile:0x007f920a13b2c8 @age=20, @name="taro">

clone_taro = taro.clone
=> #<Profile:0x007f920ab86a20 @age=20, @name="taro">

clone_taro.name.replace("clone_taro")
=> "clone_taro"

taro.name
=> "clone_taro"
```

taroの名前がcloneになってしまいました。

これを解消できるメソッドが`initialize_copy`です。

https://docs.ruby-lang.org/ja/latest/method/Object/i/initialize_copy.html

先程のProfileクラスにメソッドを追加して以下のようにします。


```ruby

class Profile
  attr_accessor :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end

  def initialize_copy(orig)
    @name = orig.name.dup
  end
end
```

この`initialize_copy`メソッドは、`dup`や`clone`メソッドを実行したときに自動的に呼び出されるメソッドです。
この中で再帰的にオブジェクトの中身に対して浅いコピーを実行する処理を入れ込むことで、

- オブジェクトそのもの
    - オブジェクトの中身

と入れ子的に浅いコピーを複数回実行して中身も複製しようというものです。

```ruby
taro = Profile.new('taro', 20)
=> #<Profile:0x007fba80bae9a0 @age=20, @name="taro">

clone_taro = taro.clone
=> #<Profile:0x007fba80fee100 @age=20, @name="taro">

clone_taro.name.replace("clone_taro")
=> "clone_taro"

taro
=> #<Profile:0x007fba80bae9a0 @age=20, @name="taro">
```

taro.nameは、taroのclone実行時にinitialize_copyメソッド内で自動的に再複製されます。そのためtaro.nameとclone_taro.nameが異なる中身となり、結果的に破壊的メソッドを実行してもオリジナルに変化はありません。

ただ、当然っちゃ当然なんですが、上記の例ではageの方はinitialize_copy内にdupの処理がないので、`clone_taro.age.replace(1000000)`などと実行するとオリジナルの方も変化します。`replace`などの置き換え処理が想定されるオブジェクトはすべて`initialize_copy`に引いておく必要があります。
また、あくまで浅いコピーの中で浅いコピーをしているだけなので、さらにもう1段深い要素に対しての破壊的メソッドには無力です。意図した値か判別させるような(例えば `is_a?(String)`など)バリデーション的処理を入れておくと良いのかなと思いました。


```ruby

kirito = Profile.new(["kazuto", "kirigaya"], 14)
=> #<Profile:0x007fba819471c0 @age=14, @name=["kazuto", "kirigaya"]>

asuna = kirito.dup
=> #<Profile:0x007fba80bb7c58 @age=14, @name=["kazuto", "kirigaya"]>


# 配列そのものは複製済みなのでオリジナルが保持される
asuna.name.replace(["asuna", "yuuki"])
=> ["asuna", "yuuki"]

kirito
=> #<Profile:0x007fba819471c0 @age=14, @name=["kazuto", "kirigaya"]>



# これはNG
kirito_sister = kirito.dup
=> #<Profile:0x007fba80d05330 @age=14, @name=["kazuto", "kirigaya"]>

kirito_sister.name.first.replace("suguha")
=> "suguha"

kirito
=> #<Profile:0x007fba819471c0 @age=14, @name=["suguha", "kirigaya"]>
```


### 3.`Marshal`クラスを使う

多次元配列などの深い要素に対して一時に完全な複製を生成したい、でもactivesupportは宗教上使えないし・・・という場合に使えるクラスです。
オブジェクトを文字列化したり、オブジェクト化した文字列を復元したりできます。
このクラスを使って対象のオブジェクトの文字列化→復元を経ると完全に違うものになります。そのため実質的に深いコピーを実現することが可能です。


```ruby

original = ["hoge","fuga"]
=> ["hoge", "fuga"]

tmp = Marshal.dump(original)
=> "\x04\b[\aI\"\thoge\x06:\x06ETI\"\tfuga\x06;\x00T"

copy = Marshal.load(tmp)
=> ["hoge", "fuga"]

copy.first.replace("piyo")
=> "piyo"

copy
=> ["piyo", "fuga"]

original
=> ["hoge", "fuga"]
```

2のような場合もご覧のとおりです。

```ruby

class Profile
  attr_accessor :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end
end

taro = Profile.new("taro", 14)
=> #<Profile:0x007fba80ee5e20 @age=14, @name="taro">

tmp = Marshal.dump(taro)
=> "\x04\bo:\fProfile\a:\n@nameI\"\ttaro\x06:\x06ET:\t@agei\x13"

clone_taro = Marshal.load(tmp)
=> #<Profile:0x007fba80e5f618 @age=14, @name="taro">

clone_taro.name.replace("clone_taro")
=> "clone_taro"

taro.name
=> "taro"
```

本来はファイルに書き出したりするのが主目的っぽいですが、
https://docs.ruby-lang.org/ja/latest/method/Object/i/clone.html
るりまにも記載のある手法なので、使って良さそうですね。

## 参考
- [シャローコピーとディープコピーの違い](https://kurochan-note.hatenablog.jp/entry/20110316/1300267023)

- [Ruby Marshalモジュールの概要をつかむ - 気軽に楽しくプログラムと遊ぶ](http://tamata78.hatenablog.com/entry/2015/11/14/214114)

- [Active Support コア拡張機能 | Rails ガイド](https://railsguides.jp/active_support_core_extensions.html)

- [ソードアート・オンラインの登場人物](https://ja.wikipedia.org/wiki/ソードアート・オンラインの登場人物#ソードアート・オンライン（SAO）)