When to use keyword arguments aka named parameters in Ruby

16,310

Solution 1

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 print_greeting(name, message = "Hello")
  puts "#{message}, #{name}"
end

print_greeting("John Connor", "Hasta la vista") 

If you use keyword arguments:

def print_greeting(message: "Hello", name:)
  puts "#{message}, #{name}"
end

print_greeting(message: "Hasta la vista", name: "John Connor") 

or even

print_greeting(name: "John Connor", message: "Goodbye")

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. This should be more or less intuitive, and arguably results in more carefully considered method signatures.

Another benefit to using keyword arguments is when you have a method that could require additional arguments in the future.

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

What if your system requirements now need to know about a person's favorite candy bar, or if they are overweight (from consuming too many of their favorite candy bars). How could you use keyword args to 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.

Solution 2

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.

Solution 3

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

Solution 4

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
Share:
16,310

Related videos on Youtube

saihgala
Author by

saihgala

Unheeded warnings, I thought, I thought of everything Across the clouds I see my code fly There's no sensation to compare with this Can't keep my eyes from the terminal screens Tongue-tied and twisted, just an earth-bound misfit #SOreadytohelp

Updated on June 07, 2022

Comments

  • saihgala
    saihgala about 2 years

    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)
    
  • Kashyap
    Kashyap over 11 years
    To add to the answer, use them when you need to assign the arguments to variables inside your method definition and avoid writing custom code yourself.
  • Aleksei Matiushkin
    Aleksei Matiushkin over 11 years
    Don’t cheat us, please. def welcome_message(message, options={:name => 'hoge'}) ; "#{message}、#{options[:name]}" ; end is the one-liner for welcome_message. The main disadvantage of this approach, though, is that one either needs to extend Hash with kinda key_valid? or to hope that keys passed are correct. KA approach lets specify exactly which keys are permitted.
  • oldergod
    oldergod over 11 years
    @mudasobwa Seems to me you are wrong; with your function, welcome_message("message", {:age => 22}) will not show no hoge in the result but will using the keyword. You function set a default value for the whole hash, not a default value for a defined key of the hash.
  • saihgala
    saihgala over 11 years
    @oldergod perhaps I was not very clear in my question. I'm looking for compelling reasons to use this feature. What you have provided is more like a one-off case, giving me a benefit of 2 lines less code and not much more. For example, one distinct advantage of KA's in a language like c# is just have a single method definition and not write tens of different method signatures to implement method overloading whereas in Ruby method overloading is not a valid paradigm so this benefit is not relevant. Another benefit is Code Readability which is relevant to Ruby.
  • Arian Faurtosh
    Arian Faurtosh over 4 years
    Is it possible to write an initializer method that is able to process both named parameters, and ordered parameters?