【问题标题】:Why is `to_ary` called from a double-splatted parameter in a code block?为什么从代码块中的双喷参数调用`to_ary`?
【发布时间】:2019-12-01 10:29:06
【问题描述】:

似乎双溅块参数在传递的对象上调用to_ary,而lambda参数和方法参数不会发生这种情况。确认如下。

首先,我准备了一个对象obj,在该对象上定义了一个方法to_ary,它返回的不是数组(即字符串)。

obj = Object.new
def obj.to_ary; "baz" end

然后,我将这个 obj 传递给具有双 splatted 参数的各种构造:

instance_exec(obj){|**foo|}
# >> TypeError: can't convert Object to Array (Object#to_ary gives String)
->(**foo){}.call(obj)
# >> ArgumentError: wrong number of arguments (given 1, expected 0)
def bar(**foo); end; bar(obj)
# >> ArgumentError: wrong number of arguments (given 1, expected 0)

从上面可以看出,只有代码块尝试通过调用(潜在的)to_ary 方法将 obj 转换为数组。

为什么代码块的双标参数与 lambda 表达式或方法定义的行为不同?

【问题讨论】:

    标签: ruby lambda parameters codeblocks


    【解决方案1】:

    我没有你的问题的完整答案,但我会分享我的发现。

    短版

    Procs 允许使用与签名中定义的参数数量不同的参数来调用。如果参数列表与定义不匹配,则调用#to_ary 进行隐式转换。 Lambda 和方法需要与其签名匹配的参数数量。不执行任何转换,这就是不调用 #to_ary 的原因。

    加长版

    您所描述的是通过 lambdas(和方法)和 procs(和块)处理参数之间的区别。看看这个例子:

    obj = Object.new
    def obj.to_ary; "baz" end
    lambda{|**foo| print foo}.call(obj)   
    # >> ArgumentError: wrong number of arguments (given 1, expected 0)
    proc{|**foo| print foo}.call(obj)
    # >> TypeError: can't convert Object to Array (Object#to_ary gives String)
    

    Proc 不需要与它定义的相同数量的 args,并且调用了 #to_ary(您可能知道):

    对于使用lambda->() 创建的proc,如果传递给proc 的参数数量错误,则会产生错误。对于使用Proc.newKernel.proc 创建的过程,额外的参数会被静默丢弃,缺失的参数会设置为nil。 (Docs)

    更重要的是,Proc 调整传递的参数以适应签名:

    proc{|head, *tail| print head; print tail}.call([1,2,3])
    # >> 1[2, 3]=> nil
    

    来源:makandraSO question

    #to_ary 用于此调整(这是合理的,因为#to_ary 用于隐式转换):

    obj2 = Class.new{def to_ary; [1,2,3]; end}.new
    proc{|head, *tail| print head; print tail}.call(obj2)
    # >> 1[2, 3]=> nil
    

    a ruby tracker中有详细描述。

    您可以看到[1,2,3] 被拆分为head=1tail=[2,3]。这与多分配中的行为相同:

    head, *tail = [1, 2, 3]
    # => [1, 2, 3]
    tail
    # => [2, 3]
    

    正如您所注意到的,当 proc 具有双重关键字 args 时,也会调用 #to_ary

    proc{|head, **tail| print head; print tail}.call(obj2)
    # >> 1{}=> nil
    proc{|**tail| print tail}.call(obj2)
    # >> {}=> nil
    

    在第一种情况下,obj2.to_ary 返回的[1, 2, 3] 数组被拆分为head=1 和空尾,因为**tail 无法匹配[2, 3] 的数组。

    Lambda 和方法没有这种行为。它们需要严格数量的参数。没有隐式转换,所以不会调用#to_ary

    我认为这个区别是在 Ruby 源码的these two lines 中实现的:

        opt_pc = vm_yield_setup_args(ec, iseq, argc, sp, passed_block_handler,
    (is_lambda ? arg_setup_method : arg_setup_block));
    

    this function。我猜#to_aryvm_callee_setup_block_arg_arg0_splat 的某个地方被调用,很可能是在RARRAY_AREF。我很想阅读此代码的注释以了解其中发生的情况。

    【讨论】:

      猜你喜欢
      • 2012-02-16
      • 1970-01-01
      • 1970-01-01
      • 2014-04-15
      • 1970-01-01
      • 1970-01-01
      • 2013-05-26
      • 1970-01-01
      • 2017-03-11
      相关资源
      最近更新 更多