TL;DR
元の質問では、現在編集されていますが、再帰と突然変異と伝播を混同しています。3 つの概念はすべて、適切な状況で、動作が期待される場合に役立つツールです。文字列がその場で変更されることや、変更がそのオブジェクトへのすべてのポインターに伝達されることを期待していないため、投稿した特定の例が混乱を招く可能性があります。
メソッドを一般化する機能は、Ruby のような動的言語でダックタイピングを可能にするものです。主な概念上のハードルは、変数がオブジェクトを指していることを理解することであり、コア ライブラリと標準ライブラリの経験によってのみ、オブジェクトが特定のメッセージにどのように応答するかを理解できるようになります。
Ruby の文字列は、単なる言語プリミティブではなく、メッセージに応答する本格的なオブジェクトです。以下のセクションでは、これがほとんど問題にならない理由と、この機能が Ruby のような動的言語で役立つ理由を説明します。また、当初期待していた動作を生成する関連メソッドについても説明します。
オブジェクトの割り当てがすべて
私の質問は、なぜこれが問題なのかということです。「b = a」を設定するとそれらが同じobject_idになるため、技術的には同じ変数文字列に2つの名前があることを理解しています。
これは、日常のプログラミングではめったに問題になりません。次の点を考慮してください。
a = 'foo' # assign string to a
b = a # b now points to the same object as a
b = 'bar' # assign a different string object to to b
[a, b]
#=> ["foo", "bar"]
変数はオブジェクトの単なるプレースホルダーであるため、これは期待どおりに機能します。オブジェクトを変数に割り当てている限り、Ruby は直感的に期待できることを行います。
オブジェクトがメッセージを受け取る
投稿された例では、実際に行っていることは次のとおりであるため、この動作に遭遇しています。
a = 'foo' # assign a string to a
b = a # assign the object held in a to b as well
b.replace 'bar' # send the :replace message to the string object
この場合、String#replaceは、 a と b の両方が指す同じオブジェクトにメッセージを送信しています。両方の変数が同じオブジェクトを保持するため、メソッドを として呼び出すか、 として呼び出すかに関係なく、文字列は置き換えられa.replace
ますb.replace
。
これはおそらく直感的ではありませんが、実際には問題になることはめったにありません。多くの場合、メソッドが内部的にオブジェクトにラベルを付ける方法を気にせずにオブジェクトを渡すことができるように、この動作は実際には望ましいものです。これは、メソッドを一般化する場合や、メソッドのシグネチャを自己文書化する場合に役立ちます。例えば:
def replace_house str
str.sub! 'house', 'guard'
end
def replace_cat str
str.sub! 'cat', 'dog'
end
critter = 'house cat'
replace_house critter; replace_cat critter
#=> "guard dog"
この例では、各メソッドは String オブジェクトを想定しています。文字列が他の場所でcritterとラベル付けされていてもかまいません。内部的には、メソッドはラベルstrを使用して同じオブジェクトを参照します。
メソッドがいつレシーバーを変更し、いつ新しいオブジェクトを返すかを知っている限り、結果に驚くことはありません。これについては後ほど詳しく説明します。
String#replace が実際に行うこと
あなたの特定の例では、 String#replaceのドキュメントがどのように混乱するかがわかります。ドキュメントには次のように記載されています。
replace(other_str) → str str
の内容と汚染度を、other_str の対応する値に置き換えます。
これが実際に意味することはb.replace
、変数への割り当てのために新しいオブジェクトを返すのではなく、実際にオブジェクトを変更する (「内容を置き換える」) ことです。例えば:
# Assign the same String object to a pair of variables.
a = 'foo'; b = a;
a.object_id
#=> 70281327639900
b.object_id
#=> 70281327639900
b.replace 'bar'
#=> "bar"
b.object_id
#=> 70281327639900
a.object_id == b.object_id
#=> true
object_id は変更されないことに注意してください。使用した特定のメソッドは、同じオブジェクトを再利用します。内容が変わるだけです。これを、オブジェクトのコピーを返すString#subのようなメソッドと比較してください。つまり、別の object_id を持つ新しいオブジェクトが返されます。
代わりに何をすべきか: 新しいオブジェクトを割り当てる
aとbが異なるオブジェクトを指すようにしたい場合は、代わりにString#subのような非変更メソッドを使用できます。
a = 'foo'; b = a;
b = b.sub 'oo', 'um'
#=> "fum"
[a.object_id, b.object_id]
#=> [70189329491000, 70189329442400]
[a, b]
#=> ["foo", "fum"]
このやや不自然な例では、変数bに割り当てられる新しいb.sub
String オブジェクトを返します。これにより、各変数に異なるオブジェクトが割り当てられます。これは、当初期待していた動作です。