为什么我们可以修改复杂参数而不是 ruby 函数中的标量?

Why can we modify complex parameters but not scalars in a ruby function?

问题 Is Ruby pass by reference or by value? 引起了很多有用的回答,但也有很多不同意见。到目前为止,我在任何答案中都没有看到任何可以解释以下内容的内容:

ruby -e "def f(x) x=7 end; a=3; f(a); print a" 打印 3.

ruby -e "def f(x) x[0]=7 end; a=[3]; f(a); print a[0]" 输出 7。

根据经验,在我看来,标量对象与更复杂的对象(例如散列和数组)之间存在某种区别,标量按值传递,复杂对象按引用传递。这将类似于 C 的语义。

我的理解是 ruby 中的所有内容都是对象,并且 none 对较早问题的回答提到了标量和复杂类型之间的区别。那么我的描述是不是有误,如果是这样,什么是更好的描述?

Ruby 是 "pass by pointer to an object."

那么现在有什么区别呢?

def f(x)
  x = 7 
end

为局部变量分配一个新值x——这个变化是局部的,因为你重新分配了一个局部变量。

def  f(x)
  x[0] = 7
end

x 引用的数组的第一个元素分配一个新值——此更改是全局的,因为您修改了一个对象。

按值传递和按引用传递之间的区别不适用于 Ruby,即来自另一种编程语言并且在 Ruby 的上下文中没有意义。

这里的术语问题是 Ruby 是 "pass by object reference",这是 "pointer to object" 在其他语言中的一种表达方式。 Ruby 中指针和引用之间的界限是模糊的,因为没有实际的指针,加上对象本身通过引用计数保存在内存中,指针最终成为引用。所以它们是对对象的引用的指针,但不是传统意义上的引用,即 hard-linked 对同一变量的引用。

每个变量,根据定义,总是代表一个对象,即使它没有被定义:nil 本身也是一个对象和数字,甚至 floating-point 也是一个对象。这使得术语 "scalar" 几乎无关紧要,Ruby 中没有其他语言中的基本类型,布尔值、数字、字符串和 class 实例之间的区别严重模糊。

一般规则是您永远无法 back-propagate 更改变量,但通过方法所做的更改会传播。要了解原因,以下是 Ruby 查看您的代码的方式:

def f(x)
  # Change value of local variable x to 7
  x = 7
end

这只是重新定义了 x 指向的对象,因为即使 7 也是一个对象。

Ruby 对其他代码的理解完全不同:

def f(x)
  # Send the []= method call to x with the argument 7
  x.send(:[]=, 7)
end

这会向 x 发送消息(方法调用)以触发 []= 方法。该方法可以对值做任何它想做的事情,但对于具有特定含义的数组、散列和复数。它更新对象 x 引用的内部状态。

您可以看到这在其他场景中的表现:

def f(x)
  x += 'y'
end

这扩展为 x = x + y,它对中间结果进行变量重新分配。原x值未修改

def f(x)
  x << 'y'
end

在这种情况下,x.send(:<<, 'y')x 进行了 in-place 修改,因此修改了原始文件。

在编写和理解 Ruby 代码时,能够识别方法调用是一件很重要的事情。有时它们甚至不是那么明显。您可能认为 = 的存在意味着 "variable assignment" 但严格来说并非如此:

def f(x)
  x.y = 'z'
end

这貌似是给xy属性赋值,其实不是,只是调用了y=方法,相当于x.send(:y=, 'z') 这是 x 可以用多种方式解释的东西。这可能会修改值,或者它可能会做一些完全不同的事情。不深入了解 x 就无从知晓。

Empirically, this looks to me like there is some kind of distinction between scalar objects and more complex objects such as hashes and arrays, with scalars being passed by value and complex objects by reference.

Ruby 中没有 "scalar object" 或 "complex object" 这样的东西。一切都是对象。时期。一切都是 pass-by-value,始终如一,无一例外。从来没有任何 pass-by-reference 发生,永远。

更准确地说,Ruby就是通常所说的call-by-object-sharing、call-by-sharing或call-by-object。这是 pass-by-value 的特例,其中传递的值始终是指向对象的指针。

闭包中的自由变量是通过引用捕获的,但这是一个不同的问题,与本问题无关。

That would be similar to the semantics of C.

不,实际上不会。 C中没有pass-by-reference,C总是pass-by-value,就像Ruby.

在 C 中,一切都是按值传递的。 ints 是按值传递的。 chars 是按值传递的。指针是按值传递的。 Ruby 类似于 C,只是只有指针; 传递的每个值都是指向对象的指针。

def f(x)
  x = 7
end

a = 3
f(a)
a #=> 3

def f(x)
  x[0] = 7
end

a = [3]
f(a)
a[0] #=> 7

这两种情况从根本上不同:在第一种情况下,您绑定一个new 值赋给方法内的参数 x。这个 re-binding 只在方法体内可见。方法参数本质上表现得像局部变量。 (事实上​​ ,如果你反思方法体的局部变量,你会看到参数出现了。)

在第二种情况下,您调用了一个改变接收者的方法。没有正在进行的任务。是的,有一个等号,但这只是 Ruby 的索引方法赋值语法糖的一部分。你真正在做什么,是在调用方法[]=, passing 0 and 7 as arguments. It is completely equivalent to calling x.[]=(0, 7); in fact, you can write it that way if you want. (Try it!) Maybe, you would be less confused, if you used the insert method instead of []=, or another method whose name more obviously screams "I am changing the array", such as clear or replace?

该数组仍然是您传递给方法的那个数组。未修改引用。只有阵列是。如果我们不能向数组中插入东西,那么数组将毫无用处,然后那些东西就会留在里面!

因此,这两种情况的区别在于,在第一种情况下,您 分配了 一个新值,即您改变了 reference,这是行不通的,因为 Ruby 是 pass-by-value。在第二种情况下,您改变了 valuedoes 起作用,因为 Ruby 不是具有纯不可变对象的纯函数式语言. Ruby 是不纯的,它确实有可变对象,如果你改变一个对象,那么对象也会改变。

我妈妈和我的理发师对我的称呼不同,但是如果我的理发师给我剪头发,我妈妈也会注意到这一点。

注意:有些对象没有改变它们的方法。这些对象是不可变的。 Integers 是这样的不可变对象,所以你永远不能用 Integers 证明上面的东西,但这纯粹是 Integers 没有变异方法这一事实的结果, 这与他们是 "scalar" 无关。如果需要,您可以拥有没有任何变异方法的复杂复合对象:here is a question about implementing a linked list in Ruby,这两个答案包含链表的三个实现,它们都是不可变的。 (免责声明:一个答案有两个实现来自我。)