【问题标题】:Pass arguments by reference to a block with the splat operator使用 splat 运算符通过引用块来传递参数
【发布时间】:2012-07-29 12:28:33
【问题描述】:

使用 splat 运算符通过引用将参数传递给块时,参数似乎被复制了。

我有这个:

def method
  a = [1,2,3]
  yield(*a)
  p a
end

method {|x,y,z| z = 0}
#=> this puts and returns [1, 2, 3] (didn't modified the third argument)

如何通过引用传递这些参数?如果我直接传递数组似乎可以工作,但是 splat 运算符在这里会更加实用、直观和可维护。

【问题讨论】:

    标签: ruby splat


    【解决方案1】:

    这里的问题是= 符号。它使局部变量z 被分配给另一个对象。

    以字符串为例:

    def method
      a = ['a', 'b', 'c']
      yield(*a)
      p a
    end
    
    method { |x,y,z| z.upcase! }   # => ["a", "b", "C"]
    

    这清楚地表明z与数组的第三个对象相同。

    这里的另一点是您的示例是数字。 Fixnums 有固定的 id;因此,您不能在保持相同对象 ID 的同时更改数字。要更改 Fixnums,您必须使用 = 为变量分配一个新数字,而不是像 inc! 这样的自更改方法(此类方法不能在 Fixnums 上存在)。

    【讨论】:

    • “这清楚地表明参数是通过引用传递的”——不,它没有。它表明 Ruby 不是一种纯粹的函数式语言。这就是它所显示的一切。在 Ruby 中,参数总是按值传递。 总是。没有例外,没有如果,没有但是。
    • 我的意思是z 不是a[2].dup,而是za[2] 共享相同的object_id(这是对对象的一种“引用”)。也许“引用”不是一个合适的词,因为它可能与其他语言中的含义不同(即“指针”)。
    • 问题是,如果参数是通过引用传递的,正如你所说,那么我们就不会进行这种对话,因为 OP 的代码会按他的预期工作,他永远不会问这个问题首先!这是一个简短的代码 sn-p,您可以使用它来确定 Ruby(或任何语言)是通过值还是通过引用传递参数:def foo(bar); bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"
    【解决方案2】:

    如何通过引用传递这些参数?

    您不能在 Ruby 中通过引用传递参数。 Ruby 是按值传递的。总是。没有例外,没有如果,没有但是。

    如果我直接传递数组似乎可以工作

    我非常怀疑这一点。您根本无法在 Ruby 中通过引用传递参数。期间。

    【讨论】:

      【解决方案3】:
      1. 在 Ruby 中,当您编写 x = value 时,您正在创建一个新的局部变量 x,无论它以前是否存在(如果它存在,则只需重新绑定名称,原始值保持不变)。因此,您将无法以这种方式就地更改变量。

      2. 整数是不可变的。因此,如果您发送一个整数,则无法更改其值。请注意,您可以更改可变对象(字符串、哈希、数组...):

        def method
          a = [1, 2, "hello"]
          yield(*a)
          p a
        end
        
        method { |x,y,z| z[1] = 'u' }
        # [1, 2, "hullo"]
        

      注意:我试图回答你的问题,现在我的观点是:更新方法或块中的参数会导致错误代码(你不再有referential transparency)。返回新值,如果愿意,让调用者自己更新变量。

      【讨论】:

      • 非常清楚,谢谢!在这个方法中让步的原因是让调用者有机会对数组的元素进行一些预处理。我喜欢你返回新值的想法。我想我会同意的。再次感谢!
      【解决方案4】:

      是的...数组包含对象的链接。在您的代码中,当您使用 yield(*a) 然后在块中您使用指向数组中 的对象的变量。现在寻找代码示例:

      daz@daz-pc:~/projects/experiments$ irb
      irb(main):001:0> a = 1
      => 1
      irb(main):002:0> a.object_id
      => 3
      irb(main):003:0> a = 2
      => 2
      irb(main):004:0> a.object_id
      => 5
      

      因此,在块中,您无需更改旧对象,您只需创建另一个对象并将其设置为变量。但该数组包含指向旧对象的链接。

      看看调试的东西:

      def m
        a = [1, 2]
        p a[0].object_id
        yield(*a)
        p a[0].object_id
      end
      
      m { |a, b| p a.object_id; a = 0; p a.object_id }
      

      输出:

      3
      3
      1
      3
      

      【讨论】:

      • 你的例子不正确,因为 number 实际上不是一个对象。试试 3.object_id 或 5.object_id,你总会得到相同的 id。
      • 有一个魔法,number的id总是'2*number+1',怎么可能得到不同的id呢? ;)
      • 我认为@SperanskyDanil 想说的是 splat 运算符是通过引用传递的。所以OP的问题是陈述不正确的假设。整数具有固定 object_ids 并且不可变的事实与此无关。你可以使用字符串,你会得到类似的输出。
      猜你喜欢
      • 2023-01-18
      • 1970-01-01
      • 2018-12-18
      • 2015-05-11
      • 2013-11-13
      • 2019-03-22
      • 2016-12-21
      • 1970-01-01
      • 2010-10-07
      相关资源
      最近更新 更多