Что делает двойной знак в вызове метода?
Во время подготовки к экзамену Ruby Association на сертифицированный Ruby Programmer я решал подготовительный тест и столкнулся с таким сценарием:
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)
Редактировать: Проверка полезности двойного восклицательного знака (ничего не найдено)
Аргументы одинаковы с этим или без него.
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_h
а также to_hash
методы не делают ничего, что не может быть сделано без него:
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}
1 ответ
Может потребоваться деструктурировать входные параметры. В таком случае простой хеш не будет работать:
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
Тем не менее, двойной знак допускает прозрачную аргументацию деструктуризации.
Если кому-то интересно, почему это может быть полезно, 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)