double-splat 在方法调用中做了什么?

What does the double-splat do in a method call?

在准备 Ruby Association Certified Ruby Programmer Exam, I was solving the prep test 时遇到了这个场景:

def add(x:, y:, **params)
  z = x + y

  params[:round] ? z.round : z
end


p add(x: 3, y: 4) #=> 7 // no surprise here
p add(x: 3.75, y: 3, round: true) #=> 7 // makes total sense
options = {:round => true}; p add(x: 3.75, y: 3, **options) #=> 7 // huh?

现在,我知道如何使用双拼音将参数中的参数转换为散列,例如:

def splat_me(a, *b, **c)
  puts "a = #{a.inspect}"
  puts "b = #{b.inspect}"
  puts "c = #{c.inspect}"
end

splat_me(1, 2, 3, 4, a: 'hello', b: 'world')

#=> a = 1
#=> b = [2, 3, 4]
#=> c = {:a=>"hello", :b=>"world"}

不过,我也知道你不能随意双拍。

options = {:round => true}
**options

#=> SyntaxError: (irb):44: syntax error, unexpected **arg
#=> **options
#=>   ^

问题:

方法 调用(不是定义)中的双拼音 (**) 有什么用?

说白了,这是什么时候:

options = {:round => true}; p add(x: 3.75, y: 3, **options)

比这个更好:

options = {:round => true}; p add(x: 3.75, y: 3, options)

编辑:测试双拼音的有用性(none 找到)

无论有没有参数都是一样的。

def splat_it(**params)
  params
end

opts = {
  one: 1,
  two: 2,
  three: 3
}

a = splat_it(opts)   #=> {:one=>1, :two=>2, :three=>3}
b = splat_it(**opts) #=> {:one=>1, :two=>2, :three=>3}

a.eql? b # => true

我的意思是,您甚至可以毫无问题地将散列传递给使用关键字参数定义的方法,它会智能地分配适当的关键字:

def splat_it(one:, two:, three:)
  puts "one   = #{one}"
  puts "two   = #{two}"
  puts "three = #{three}"
end

opts = {
  one: 1,
  two: 2,
  three: 3
}

a = splat_it(opts)   #=> {:one=>1, :two=>2, :three=>3}
#=> one   = 1
#=> two   = 2
#=> three = 3

b = splat_it(**opts) #=> {:one=>1, :two=>2, :three=>3}
#=> one   = 1
#=> two   = 2
#=> three = 3

使用适当的 to_hto_hash 方法在随机 class 上进行双重 splat 不会做任何没有它无法完成的事情:

Person = Struct.new(:name, :age)

Person.class_eval do
  def to_h
    {name: name, age: age}
  end

  alias_method :to_hash, :to_h
end

bob = Person.new('Bob', 15)

p bob.to_h #=> {:name=>"Bob", :age=>15}


def splat_it(**params)
  params
end

splat_it(**bob) # => {:name=>"Bob", :age=>15}
splat_it(bob)   # => {:name=>"Bob", :age=>15}

可能需要解构输入参数。在这种情况下,简单的散列将不起作用:

params = {foo: 42, bar: :baz}
def t1(foo:, **params); puts params.inspect; end
#⇒ :t1
def t2(foo:, params); puts params.inspect; end 
#⇒ SyntaxError: unexpected tIDENTIFIER
def t2(params, foo:); puts params.inspect; end
#⇒ :t2

现在让我们测试一下:

t1 params
#⇒ {:bar=>:baz}
t2 params
#⇒ ArgumentError: missing keyword: foo
t2 **params
#⇒ ArgumentError: missing keyword: foo

也就是说,double splat 允许透明参数解构。

如果有人好奇为什么它可能有用,上例中的 foo 在调用此语法中的方法时成为 必需的 参数。


函数调用中取消拼接参数允许作为一种健全类型检查以确保所有键都是符号:

h1 = {foo: 42}
h2 = {'foo' => 42}
def m(p); puts p.inspect; end
m **h1
#⇒ {:foo=>42}
m **h2
#⇒ TypeError: wrong argument type String (expected Symbol)