プラグインをきれいに書くために

fspace

ユーザー
英語版のWikipediaによると、「シャローコピー」「ディープコピー」という用語はSmalltalk-80で使われたのが最初だそうです。

There are two ways to make copies of an object. The distinction is whether or not the values of the object's variables are copied. If the values are not copied, then they are shared (shallowCopy); if the values are copied, then they are not shared (deepCopy).

とあるので、「シャローコピー」「ディープコピー」は元々、オブジェクトの複製をつくる方法(操作)で間違いないようです。

2の意味について、「Aをシャロー/ディープコピーした結果のオブジェクト」やそれに類するものを指して「Aのシャロー/ディープコピー」と呼ぶことはありますが、あくまでも1の意味を前提とした言葉だと思います。オブジェクトを複製していない参照のコピーを「シャローコピー」と呼んだり、操作と結果を逆転させて「このときのsliceはディープコピーだ」と言うのはおかしな感じがします。
 
たしかに英語版のWikipedia のシャロー/ディープコピーの説明を見る限りでは、結果主導の考え方ではなく、コピーの方法の違いであると書いてあると思います。
オブジェクトを複製していない参照のコピーを「シャローコピー」と呼んだり、操作と結果を逆転させて「このときのsliceはディープコピーだ」と言うのはおかしな感じがします。
できた結果のオブジェクトから逆説的に考えて、BはAのシャロー/ディープコピーをしたものだ と言っているのだと思います。
AをBにコピーしたときBがAと参照を共有しているなら、BはAとシャローコピーされた。
AをBにコピーしたときBとAが互いに独立している(変更が影響を及ぼさない)なら、BはAとディープコピーされた。
というように結果主導で判断する考え方もあると思っています。

元々の考え方としては「コピーの方法の種類」を表していたのかもしれませんが、他者にコピー後のオブジェクトの状態を説明するときに
説明しやすいなどの理由によりこういった考え方が生まれたというところでしょうか。
現にこういった使い方を全くしないということはないのではないでしょうか(全くしない人もいるかもしれませんが)
ネット上の情報を見る限りではどちらの意見もあるといったところだと思います。

長くなりましたが、お陰様で頭の中のモヤモヤが晴れた気がします。定義を考えるときはWikipediaの利用も検討することにしたいと思います。
この度は知見共有含め、このような質問に付き合って頂きありがとうございました。今後に役立てていければと思います。
 
There are two ways to make copies of an object. The distinction is whether or not the values of the object's variables are copied. If the values are not copied, then they are shared (shallowCopy); if the values are copied, then they are not shared (deepCopy).
この引用文について考えてみました。日本語にするとおおよそ以下の内容になると思います。
・オブジェクトのコピーの方法は2通りある。
・違いはオブジェクトの変数の値がコピーされるかどうかだ。
・もし値がコピーされないとき、それらは共有されている(シャローコピー)
・もし値がコピーされるとき、それらは共有されない(ディープコピー)

上で書かれている内容だと微妙に意味がわかりにくいので用語を加えてみると
・値のコピーを行わず、参照が共有されるコピーの方法をシャローコピーという。
・値のコピーを行い、参照が共有されないコピーの方法をディープコピーという。
と解釈できないでしょうか。

つまり以前の返信で自分が提示した
JavaScript:
const sorce = [1, 2, 3];
const copy1 = sorce; //シャローコピー
const copy2 = sorce.slice(); //ディープコピー
これは上記の解釈に即した内容であるといえないでしょうか。
なぜなら、2行目のコピーは値のコピーを行わない参照のコピーで、値が共有されるコピーであり、
3秒目のコピーは値のコピーを行い、参照の共有がされないコピーの方法であるからです。
 

fspace

ユーザー
つまり以前の返信で自分が提示した
JavaScript:
const sorce = [1, 2, 3];
const copy1 = sorce; //シャローコピー
const copy2 = sorce.slice(); //ディープコピー
これは上記の解釈に即した内容であるといえないでしょうか。
なぜなら、2行目のコピーは値のコピーを行わない参照のコピーで、値が共有されるコピーであり、
3秒目のコピーは値のコピーを行い、参照の共有がされないコピーの方法であるからです。

言えないと思います。

原文を単純化すると、「オブジェクトの複製方法には『シャローコピー』と『ディープコピー』の二つがある」ということになります。

2行目については、そもそもオブジェクトを複製していないため、『シャローコピー』でも『ディープコピー』でもありません。

3行目について、sliceは複製方法なので、『シャローコピー』か『ディープコピー』かのどちらかです。対象によって挙動を変えることはありません。『ディープコピー』だとすると、配列に参照が含まれる場合におかしくなるので、sliceは『シャローコピー』です。

sorce.slice()の結果は『ディープコピー』である」と言うことはあると思いますが、これは「『ディープコピー』した場合と同じ結果のオブジェクトである」という意味です。『ディープコピー』した場合と同じ結果のオブジェクトを生成したという結果から、sliceが『ディープコピー』を行ったと言ってしまうと相手を誤解させかねません。
 
原文を単純化すると、「オブジェクトの複製方法には『シャローコピー』と『ディープコピー』の二つがある」ということになります。
原文が
There are two ways to make copies of an object. The distinction is whether or not the values of the object's variables are copied. If the values are not copied, then they are shared (shallowCopy); if the values are copied, then they are not shared (deepCopy).
であるのに対し、要約がそれになるとすると
The distinction is whether or not the values of the object's variables are copied.
などの部分はどこに消えてしまったのかということになると思うのですが…。

この原文の一番の論点は
If the values are not copied, then they are shared (shallowCopy); if the values are copied, then they are not shared (deepCopy).
であると自分は思います。
「参照が共有されるコピー方法をシャローコピー、参照が共有されないコピー方法をディープコピーという」
というような内容になるかと思います。更に拡大解釈を含めると、
「完全なコピーがディープコピーであり、参照の共有を含むコピーをシャローコピーという」
あたりがより適切な解釈ではないかと考えます。

2行目については、そもそもオブジェクトを複製していないため、『シャローコピー』でも『ディープコピー』でもありません。
オブジェクトの代入演算子による参照のコピーは「オブジェクトのコピー」に相当すると思います。(javascript)
イメージを図にすると下記サイトの表現がマッチしていると思います。
3行目について、sliceは複製方法なので、『シャローコピー』か『ディープコピー』かのどちらかです。対象によって挙動を変えることはありません。『ディープコピー』だとすると、配列に参照が含まれる場合におかしくなるので、sliceは『シャローコピー』です。
sliceによって、新しい参照に配列内の全要素がコピーされ、コピー元とコピー先で別々の参照を指した状態になっている。これがディープコピーの状態を表している。というのが私の主張です。これはコードの通り悪魔で元データが1次元のプリミティブ型の配列である場合で、参照を含む配列の場合はディープコピーではなくなります。
sliceがディープコピーだと言っている訳ではなく、この元データをディープコピーするにはslice相当のコピーで十分だという意味です。

内容が以前の返信の繰り返しになってきていると思います。
結局のところ、シャロー/ディープコピーが何を表しているのかというのは、定義としてあげて頂いた英語の原文を最大限意訳して意味を紐解いて行くしかないと思います。自分は上で上げている内容であると解釈しましたが、fspaceさんとの解釈とは食い違っているというところが問題であると感じています。
 
最後に編集:
Smalltalk80の全文が下記ページで見れたので考察をまとめてみました。
(下図で使用されている英文、Smalltalkコード、図は上サイト内からの引用したものです)
シャローコピー説明Smalltalk80.png
まとめると、シャローコピーの定義は
「別々のオブジェクトの要素が同じ参照を共有している」
ということになると思います。

SmallTalkにはプリミティブ型がなく全てがオブジェクトであるということ、
配列の要素を置き換えの操作が、参照先要素の置き換えではなく、別の参照へのつなぎ直しである
という点がjavascriptでの動作と根本的に違う部分であるようでした。
putに関する補足:https://stackoverflow.com/questions/50286689/how-does-shallow-copy-work-for-array-in-smalltalk

シャローコピーの例をjavascriptでのコードで表すと次のようになると思います。
JavaScript:
const sorce = [[1,2],[3,4]];//オブジェクトの中身は参照のみ
const copy = [];
for(let i=0; i<sorce.length; i++) {
  copy[i] = sorce[i];
}
console.log(sorce); //[ [ 1, 2 ], [ 3, 4 ] ]
console.log(copy);  //[ [ 1, 2 ], [ 3, 4 ] ]
console.log(sorce === copy); //false 別々のオブジェクトである

//要素は参照を共有しているので変更が他方に影響を及ぼす。
sorce[0][0] = 5;
console.log(sorce); //[ [ 5, 2 ], [ 3, 4 ] ]
console.log(copy);  //[ [ 5, 2 ], [ 3, 4 ] ]

オブジェクトの代入は同一オブジェクトを指すことになるのでシャローコピーではない。

以上が今回導きだした結論です。今度こそ正しいと思います。

※追記 プリミティブ型の配列をsliceでコピーしたとき、それがディープコピーなのかどうかについて
javascriptのコードで示すと次のようになると思います。
JavaScript:
const sorce = [1,2,3];
const copy = sorce.slice();

console.log(sorce); //[ 1, 2, 3 ]
console.log(copy);  //[ 1, 2, 3 ]
console.log(sorce === copy); //false 別々のオブジェクトである

//参照を共有していないため変更は他方に影響を及ぼさない。
sorce[0] = 5;
console.log(sorce); //[ 5, 2, 3 ]
console.log(copy);  //[ 1, 2, 3 ]

Smalltalk80にはプリミティブ型がないので、定義の範囲外になるのだと思います。
ただ、要素が参照を共有していないという点からシャローコピーではないと言えると思います。
そして、ディープコピーなのかについては、「参照が共有されていない、それぞれの要素がコピーされた状態である」という点から
「ディープコピーと判断しても良い」というのが私の見解となります。
 
最後に編集:

fspace

ユーザー
ややこしくなってきたので少し整理します。

現在、『シャローコピー』『ディープコピー』という言葉は次の二つの使われ方をします。
  1. オブジェクトの複製を生成する操作  e.g. 「関数fは配列をシャローコピーする」
  2. 複製したオブジェクトと元のオブジェクトとの関係性  e.g. 「配列aは配列bのディープコピーである」
元々、1の意味として定義され、1の操作の結果をも同じ言葉で呼んでいたため、俗語的に2の意味でも使われるようになったという印象です。

このため、「『シャローコピー』によって『ディープコピー』を生成する」という表現も一応は意味が通ることになります(あまり正式な表現とは言えないかもしれませんが)。前者の『シャローコピー』が1の意味で、後者の『ディープコピー』が2の意味です。そして、この表現を認めるのであれば、「『ディープコピー』を生成するからこの操作は『ディープコピー』だ」は誤りになります。

まとめると、シャローコピーの定義は
「別々のオブジェクトの要素が同じ参照を共有している」
ということになると思います。

Smalltalk-80の時点では1の意味でのみ使用されていたと思いますが、2の意味としては解釈できると思います。

SmallTalkにはプリミティブ型がなく全てがオブジェクトであるということ、
配列の要素を置き換えの操作が、参照先要素の置き換えではなく、別の参照へのつなぎ直しである
という点がjavascriptでの動作と根本的に違う部分であるようでした。

プリミティブ型によってSmalltalkと事情が異なる部分が出てきてしまっているのは確かだと思います。参照のつなぎ直しであることはJavaScriptでも同じですが、これも要素がプリミティブ型の場合には解釈が分かれると思います。

オブジェクトの代入は同一オブジェクトを指すことになるのでシャローコピーではない。

はい、1の意味においてはオブジェクトを複製していないので明らかにシャローコピーではありません。2の意味においても、コピーという言葉からして、同一オブジェクトをシャローコピーと呼ぶことはないと思います。

ただ、要素が参照を共有していないという点からシャローコピーではないと言えると思います。
そして、ディープコピーなのかについては、「参照が共有されていない、それぞれの要素がコピーされた状態である」という点から
「ディープコピーと判断しても良い」というのが私の見解となります。

2の意味で、結果のオブジェクトを指して、ディープコピーと呼ぶことは問題ないと思います。しかし、結果を元に操作自体をディープコピーと呼ぶのであれば、1の意味と食い違うので間違いだと思います。1の意味を元にした2の意味で、1の意味と矛盾する再定義を行うのはさすがに変です。
 
返信ありがとうございます。
プリミティブ型が含まれるオブジェクトについて考慮しないことにすれば、この問題に対する見解は概ね一致できたように思います。
理解を深めることができたと思います。ありがとうございました。

※追記 自分の見解について説明しきれていない部分があったため追記しました。

厳密な話を引き合いに出す場合、プリミティブ型がある言語において、
JavaScript:
const copy = sorce.slice();
とだけ書いてあった場合にこの操作は「シャローコピーである」とは完全には言えないのではないでしょうか。

Smalltalk-80において「copy操作はシャローコピーである」と記載があることからシャローコピーが操作を表しているのは
間違いないと思いますが、それと同時にcopy操作を行ったあとのコピー元とコピー先オブジェクトの関係性について
コードや図を用いて詳細に説明されています。(Smalltalk-80 pp97~99、1枚の図としてまとめたものが前の返信にあります)
その中でシャローコピーをすることで、コピー元とコピー先は別々のオブジェクトを参照し、同時に同じ要素を共有するようになる。
とあります。
この点がシャローコピーを説明する上での重要な要素であると判断し、故にこれをシャロコピーの定義として挙げさせて頂きました。
またpp97の冒頭においても
If the values are not copied, then they are shared (shallowCopy)
として参照が共有される状態がシャローコピーを表していると説明されています。
これはオブジェクト型しか存在しない言語においては、単にコピーするという操作が参照のコピーとなり、結果として参照を共有した
状態になる。という意味を含んだものと思われます。

コードに話を戻します。例えば次のコードの場合、
JavaScript:
const sorce = [[1,2],[3,4]];
const copy = sorce.slice();
これは完全にシャローコピーであると言えると思いますが、
JavaScript:
const sorce = [1,2,3];
const copy = sorce.slice();
この場合は完全にはシャローコピーであるとは言えないと思います。
シャローコピーが単に全要素をコピーする操作である。というのはわかります。
ただそれと同時に「別々のオブジェクトが同じ要素を共有している」という点がシャローコピーを表す上で必要な事象であると
判断するからです。

ここで仮にシャロー/ディープコピーの定義が次のものであるとします。
・参照が共有されるコピーが行われたとき、それをシャローコピーという。
・参照が共有されないコピーが行われたとき、それをディープコピーという。

と、ここまで書いて気づいたことがあります。それはSmalltalk-80 pp97冒頭の定義文
If the values are not copied, then they are shared (shallowCopy);
if the values are copied, then they are not shared (deepCopy).
と内容が一致しているということです。

定義がこれであるならば、プリミティブ型、参照型関わらず、あらゆるコピー操作について説明することができます。
(ここで、定義文のobject'sにはプリミティブ型、参照型どちらも当てはめて考えて良いこととします)
JavaScript:
const a = 10;
const b = a; //ディープコピー
//参照が共有されないコピーに該当するためディープコピーであると判断できる。

const a2 = [1,2,3];
const b2 = a2; //シャローコピー
//C++でいうところのポインタ変数のコピーであると考えるなら、2つのポインタ変数が同一のメモリアドレスを
//指すことになる。つまり参照が共有されるコピーに該当するためシャローコピーと判断できる。
 
const a3 = [1,2,3];
const b3 = a3.slice(); //ディープコピー
//ここでいうsliceとはオブジェクトの全ての要素について単にコピーする操作を指し
//for(let i=0; i<a3.length; i++) b3[i] = a3[i]; と同義であるとする。
//参照が共有されないコピーに該当するためディープコピーであると判断できる。

const a4 = [[1,2],[3,4]];
const b4 = a4;
//a2同様、ポインタ変数のコピーと考えることができるためシャローコピーであると判断できる。

const a5 = [[1,2],[3,4]];
const b5 = a5.slice();
//b5[0] = a5[0] とb5[1] = a5[1]のコピーがポインタ変数のコピーに該当し、
//参照を共有するためシャローコピーであると判断できる。

const a6 = [[1,2],[3,4]];
const b6 = [];
for(let i=0; i<a6.length; i++) {
  const tmp = [];
  for(let j=0; j<a6[0].length; j++) {
    tmp[j] = a6[i][j];     
  }
  b6[i] = tmp;
}
//参照を共有しないコピーに該当するためディープコピーと判断できる。

現在、『シャローコピー』『ディープコピー』という言葉は次の二つの使われ方をします。
  1. オブジェクトの複製を生成する操作  e.g. 「関数fは配列をシャローコピーする」
  2. 複製したオブジェクトと元のオブジェクトとの関係性  e.g. 「配列aは配列bのディープコピーである」
元々、1の意味として定義され、1の操作の結果をも同じ言葉で呼んでいたため、俗語的に2の意味でも使われるようになったという印象です。
シャロー/ディープコピーが元々は操作という意味で使われており、後から関係性としての意味が生まれたとありますが、
Smalltalk-80のpp97~99の説明を見る限りでは、すでに関係性についての説明がされており、この時点で既に
シャロー/ディープコピーという言葉が操作と関係性の2つの意味をもっていたと解釈することができると私は考えます。

There are two ways to make copies of an object. The distinction is whether or not the values of the object's variables are copied. If the values are not copied, then they are shared (shallowCopy); if the values are copied, then they are not shared (deepCopy).
最後に、この定義文中でいうところのobjectとは、Smalltalk-80がオブジェクト型しか持たない言語であるからそうしているだけで、
実際には他の型でも問題なく、型がobjectかどうかというのはこの定義からすると些細な問題でしかなかった、ということになるのかと思います。
 
最後に編集:

fspace

ユーザー
If the values are not copied, then they are shared (shallowCopy)

おそらくこの文章を「値がコピーされない場合、値は共有される(この状態をシャローコピーという)」と解釈しているのだと思いますが、その前に「オブジェクトを複製する方法は二つある」と書いてあることや、"shallowCopy"がキャメルケースで書かれていて直後に"Object instance protocol"として挙げられていることを考えれば、これは「値がコピーされない場合、値は共有される(これを行うのがshallowCopyである)」と解釈するのが妥当ではないでしょうか。ここで、shallowCopyは操作で、「値は共有される」は状態なので、shallowCopyと同格なのは「値がコピーされない」の方だと思います。つまり、「shallowCopyの場合、値は共有される」という意味になります。

ちなみに、ここで「値が共有される」という結果が得られるのはSmalltalkにオブジェクトしか存在しないためだと思われます(Smalltalkはよく知らないので推測でしかありませんが)。JavaScriptでプリミティブ型を考慮した場合、これが満たされるとは限りません。従って、「値が共有される」ならば「シャローコピーである」が成り立たないのみならず、「シャローコピーである」ならば「値が共有される」も成り立ちません。

最後に、この定義文中でいうところのobjectとは、Smalltalk-80がオブジェクト型しか持たない言語であるからそうしているだけで、
実際には他の型でも問題なく、型がobjectかどうかというのはこの定義からすると些細な問題でしかなかった、ということになるのかと思います。

もちろん、Smalltalkにおける『シャローコピー』や『ディープコピー』をオブジェクト以外にも拡張できると主張すること自体はできると思います。ただし、そのように使って相手に伝わるかどうかは別問題です。結局のところただの言葉なので、相手に正しく意味を伝えることができるかどうかが重要なポイントになります。
 

もふもふ

ユーザー
すごいレベルの話が展開されていてひいてしまう、ひいてしまう。

実装していて思うのは、一般人としてコピーと言えば
値が複製されるのがさも当たり前と思われやすいですね。
(ポインターのない言語でこれを若い子に説明するのが難しい難しい。)

プリミティブならコンパイラが型を把握しているので値の複製が実装なしでできますが(シャローコピーと捉える)
それ以外の場合は、例えば自作クラスに合ったコピーを作ってやらないといけない(ディープコピーと捉える)。

ディープコピーを完全なる全コピーという概念に置き換えて・・・。
巨大なインスタンスを複製するレスポンスの是非とかは無視して使う側として
「Cloneのインタフェースあるならディープコピーしとけや!」って、暴論を思うこともしばしなんですが
「これ、全コピーちゃうねん」っていうためにシャローコピーがあるくらいの文言の使い分けですね、わたしは。

雑魚いわたしは厳密なことは理解できませんが、このツリーはJavaScriptの勉強が進んで非常にありがたいです。
ありがとうございます!!
 

fspace

ユーザー
実装していて思うのは、一般人としてコピーと言えば
値が複製されるのがさも当たり前と思われやすいですね。
(ポインターのない言語でこれを若い子に説明するのが難しい難しい。)

確かにGC中心の言語だとメモリの存在が隠されてしまうので参照という概念自体難しいのかもしれませんね。

プリミティブならコンパイラが型を把握しているので値の複製が実装なしでできますが(シャローコピーと捉える)
それ以外の場合は、例えば自作クラスに合ったコピーを作ってやらないといけない(ディープコピーと捉える)。

できるできないは単に言語の問題かなと思います。JavaScriptだとクラスごとにディープコピー用の関数を用意する必要はないですし、静的型付け言語であってもコンパイラによる自動生成は可能です。

「これ、全コピーちゃうねん」っていうためにシャローコピーがあるくらいの文言の使い分けですね、わたしは。

元々はそうだったんじゃないかと自分も思いますが、オブジェクト自体の同一性を判断するのに参照が使えるということで、シャローコピーにも意味ができてしまったので、単純にそうでないものとして捉えてしまうわけにもいかないかなと。
 
トップ