TL;DR
在您现在已编辑的原始问题中,您将递归与变异和传播混淆了。这三个概念都是在正确的情况下以及预期行为时有用的工具。您可能会发现您发布的特定示例令人困惑,因为您不希望字符串在适当的位置发生变化,或者更改传播到指向该对象的所有指针。
泛化方法的能力是在 Ruby 等动态语言中实现鸭式类型的原因。主要的概念障碍是理解变量指向对象,只有使用核心库和标准库的经验才能让您了解对象如何响应特定消息。
Ruby 中的字符串是响应消息的成熟对象,而不是简单的语言原语。在接下来的部分中,我将尝试解释为什么这很少会成为问题,以及为什么该功能在像 Ruby 这样的动态语言中很有用。我还介绍了一个相关的方法,它会产生您最初期望的行为。
都是关于对象分配的
我的问题是为什么会这样。我知道设置“b=a”会使它们成为相同的 object_id,因此从技术上讲,同一个变量字符串有两个名称。
这在日常编程中很少出现。考虑以下几点:
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"]
这符合您的预期,因为变量只是对象的占位符。只要您将 objects 分配给变量,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.sub 返回一个 new 字符串对象,然后将其分配给变量 b。这导致将不同的对象分配给每个变量,这是您最初期望的行为。