Ruby 2.0.0 supports keyword arguments (KA) and I wonder what the benefits/use-cases are of this feature in context of pure Ruby, especially when seen in light of the performance penalty due to the keyword matching that needs to be done every time a method with keyword arguments is called.
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)
Keyword arguments have a few distinct advantages no one has touched on.
First off you are not coupled to the order of the arguments. So in a case where you might have a nil argument occasionally it looks a lot cleaner:
def yo(sup, whats="good", dude="!")
# do your thing
end
yo("hey", nil, "?")
if you use keyword arguments:
def yo(sup:, whats:"good", dude:"!")
# do your thing
end
yo(sup: "hey", dude: "?")
or even
yo(dude: "?", sup: "hey")
It removes the need to have to remember the order of the arguments. However, the disadvantage is you have to remember the argument's name, but that should be more or less intuitive.
Also, when you have a method that could possibly have a need to take more arguments in the future.
def create_person(name:, age:, height:)
# make yourself some friends
end
what if your system all of the sudden wants to know about a person's favorite candy bar, or if they are overweight (from consuming too many of their favorite candy bar), how would you do that? Simple:
def create_person(name:, age:, height:, favorite_candy:, overweight: true)
# make yourself some fat friends
end
Before keyword arguments there was always the hash, but that led to a lot more boilerplate code to extract and assign variable. Boilerplate code == more typing == more potential typos == less times writing awesome ruby code.
def old_way(name, opts={})
age = opts[:age]
height = opts[:height]
# all the benefits as before, more arthritis and headaches
end
If you are just setting up a method that takes one argument and will most likely never have a need to change:
def say_full_name(first_name, last_name)
puts "#{first_name} #{last_name}"
end
Then keyword arguments should be avoided, since there is a small performance hit.
Since KA are ruby-wide innovation, I see two main advantages:
- limit permitted arguments to a predefined set, as Rails does with assert_valid_keys;
- use the feature within code blocks.
The summing up:
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
The inefficiency issue of using keyword arguments no longer seems to be a problem as of ruby-2.2.0.
Feature #10440 fixed the speed issue and was released in ruby-2.2.0:
Mon Nov 03 03:02:38 2014 Koichi Sasada
- rewrite method/block parameter fitting logic to optimize keyword arguments/parameters and a splat argument. Feature #10440 (Details are described in this ticket)
You can see this for yourself (using the same code as given in the original question):
(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)
There's still an extremely negligible performance penalty for using keyword args, but I think it's an acceptable tradeoff in exchange for the benefit of increased readability and positional flexibility.
For example
A function
def welcome_message(message, options={})
default_options = {name: 'hoge'}
options = default_options.merge(options)
"#{message}、#{options[:name]}"
end
could be written
def welcome_message(message, name: 'hoge')
"#{message}、#{name}"
end
来源:https://stackoverflow.com/questions/15062570/when-to-use-keyword-arguments-aka-named-parameters-in-ruby