Когда использовать аргументы ключевого слова aka именованные параметры в Ruby

Ruby 2.0.0 поддерживает аргументы ключевых слов (KA), и мне интересно, какие преимущества/варианты использования этой функции в контексте чистого Ruby, особенно когда они видны с учетом снижения производительности из-за соответствия ключевых слов, которое необходимо выполняться каждый раз, когда вызывается метод с аргументами ключевого слова.

require 'benchmark'

def foo(a:1,b:2,c:3)
  [a,b,c]
end

def bar(a,b,c)
  [a,b,c]
end

number = 1000000
Benchmark.bm(4) do |bm|
  bm.report("foo") { number.times { foo(a:7,b:8,c:9)  } }
  bm.report("bar") { number.times { bar(7,8,9) } }
end

#           user     system      total        real
# foo    2.797000   0.032000   2.829000 (  2.906362)
# bar    0.234000   0.000000   0.234000 (  0.250010)

Ответ 1

Аргументы ключевых слов имеют несколько отличительных преимуществ, которые никто не затронул.

Во-первых, вы не связаны с порядком аргументов. Так что в случае, когда у вас иногда бывает ноль, он выглядит намного чище:

def yo(sup, whats="good", dude="!")
  # do your thing
end

yo("hey", nil, "?")

если вы используете аргументы ключевого слова:

def yo(sup:, whats:"good", dude:"!")
  # do your thing
end

yo(sup: "hey", dude: "?")

или даже

yo(dude: "?", sup: "hey")

Это устраняет необходимость запоминать порядок аргументов. Однако недостатком является то, что вы должны помнить имя аргумента, но это должно быть более или менее интуитивно понятным.

Кроме того, если у вас есть метод, который, возможно, потребуется больше аргументов в будущем.

def create_person(name:, age:, height:)
  # make yourself some friends
end

что, если ваша система внезапно хочет узнать о любимой конфетке человека, или если у них избыточный вес (из-за потребления слишком многих их любимых конфет), как бы вы это сделали? Простой:

def create_person(name:, age:, height:, favorite_candy:, overweight: true)
  # make yourself some fat friends
end

До аргументов ключевого слова всегда существовал хеш, но это привело к значительному расширению кода шаблона для извлечения и назначения переменной. Код коаксиального кода == more typing == больше потенциальных опечаток == меньше времени написания удивительного рубинового кода.

def old_way(name, opts={})
  age    = opts[:age]
  height = opts[:height]
  # all the benefits as before, more arthritis and headaches  
end

Если вы просто настраиваете метод, который принимает один аргумент и, скорее всего, никогда не будет нуждаться в изменении:

def say_full_name(first_name, last_name)
  puts "#{first_name} #{last_name}"
end

Затем следует избегать аргументов ключевого слова, так как есть небольшое поражение производительности.

Ответ 2

Поскольку KA - инновация в рубиновом масштабе, я вижу два основных преимущества:

  • ограничить допустимые аргументы предопределенному набору, так как Rails делает с assert_valid_keys;
  • используйте эту функцию в кодовых блоках.

Подводя итог:

a = lambda { |name: "Leonardo", age: 67| [name, age] }
a.call # ⇒ ["Leonardo", 67]
a.call name: "Michelangelo", age: 88 # ⇒ ["Michelangelo", 88]
a.call name: "Schwarzenegger", alive: true # ⇒ ArgumentError: unknown keyword: alive

Ответ 3

Проблема неэффективности использования аргументов ключевого слова больше не является проблемой с ruby-2.2.0.

Функция # 10440 исправила проблему с пропускной способностью и была выпущена в ruby- 2.2.0:

Пн ноя 03 03:02:38 2014 Коичи Сасада

  • переписать метод/блок привязки параметров блока для оптимизации аргументов/параметров ключевого слова и аргумента splat. Функция # 10440 (Подробности описаны в этом билете)

Вы можете это увидеть сами (используя тот же код, что и в исходном вопросе):

(08:04:%) rvm use ruby-2.0.0-p247
Using /Users/adam/.rvm/gems/ruby-2.0.0-p247

(08:04:%) ruby keyword_benchmarks.rb

       user     system      total        real
foo    1.390000   0.060000   1.450000 (  1.451284)
bar    0.130000   0.000000   0.130000 (  0.122344)

(08:04:%)   rvm use ruby-2.2.0
Using /Users/adam/.rvm/gems/ruby-2.2.0

(08:04:%) ruby keyword_benchmarks.rb

       user     system      total        real
foo    0.140000   0.000000   0.140000 (  0.136194)
bar    0.110000   0.000000   0.110000 (  0.116246)

По-прежнему крайне незначительное снижение производительности для использования ключевых слов args, но я думаю, что это приемлемый компромисс в обмен на пользу большей читаемости и позиционной гибкости.

Ответ 4

Например

Функция

def welcome_message(message, options={})
  default_options = {name: 'hoge'}
  options = default_options.merge(options)

  "#{message}、#{options[:name]}"
end

может быть записано

def welcome_message(message, name: 'hoge')
  "#{message}、#{name}"
end