1. Computing

Keyword Arguments

By

Keyword Arguments

Keyword arguments are a powerfully expressive feature in any language. You'd think they would be a staple in Ruby's toolchest of awesome features. You may also think that Ruby already has them, certainly you've used them in a myriad of Rails-related method calls such as url_for, right? Well no, you haven't seen them in Ruby before, you've been using a hack. Ruby hasn't had keyword arguments, just the ability to have hash key/value pairs exist in the argument list without any hash decoration. But with Ruby 2.0, Ruby now has real keyword arguments.

For a good example of what keyword arguments are all about and how they're used, take a look a the url_for method in ActiveRecord (part of Ruby on Rails). It will spit out a URL for an action/controller pair, but also takes a whole host of optional arguments that you won't need 99% of the time. If you've ever spent any time programming APIs like the Win32 API, you know just how annoying it is to pass values for 15 (no, really, some of the functions in that API have a ridiculous amount of arguments) arguments you don't need when you just want to do something simple. Keyword arguments are used to let you specify by name which arguments you're referring to, and let the method decide what to do with the arguments you didn't specify. So when using url_for, you often only pass in the :action key and don't pass anything for the rest of the arguments. The method is still complex, taking tens of arguments, but still expressive since you only need to pass what you need.

The Old Way

Ruby has been able to do this type of keyword argument for some time now, but only if you rely on something that's a bit of a hack. If one of your arguments has a default value of a hash, then any "naked" (that is, without the normal curly braces that decorate hashes) key/value pairs in the method call syntax are stuffed into this hash, but it's far from ideal and doesn't work like a true keyword parameter.

If you define any default values for the values in the hash in the method declaration and you pass any values when invoking the method, the values used when invoking the method create a totally new hash and don't merge with the hash you defined. Look at this test method. Its default hash parameter has a single key/value in it already.


def test(opts={a:10})
  puts opts.inspect
end

If you were to call this method without any arguments, it works as expected. The :a value in the options hash is 10. If you were to call it with test(a:20), again it works as expected, the value is 20. However, if you were to call it with test(b:10), something goes wrong. The arguments that you pass when calling the method don't merge with the default hash, they overwrite it. Now, the options hash will be equivalent to {b:10}, there is no :a key. See what happens when you try something a bit more practical.


def test(opts={option1:true, option2:false})
  puts opts.inspect
end

The intent here is clear, the programmer wished to provide default values for both of the keyword arguments should the user want to pass zero or only one value, instead of two. However, passing only one value such as test(option2:true) will erase the value for option1 since it creates a totally new hash containing only an option2 key. This is very annoying, and the solution is often to start with a blank hash and merge the user-supplied arguments on top of your default options. This is not expressive, but it works.


def test(opts={})   # Note the empty hash
  defaults = {option1:true, option2:false}
  opts = defaults.merge(opts)

  puts opts.inspect
end

Now when you call test(option1:false), both arguments are preserved, just as you'd expect. However, there are two extra lines here, and it is really a hack. Plus I'm sure creating all these hash object can't be good for performance. It's a trick you learn about down the road, it's not a clean language feature. But things are different in Ruby 2.0.

The New Way

Ruby 2.0 introduces proper keyword arguments. You don't need the default hash anymore, you just need to define default values for the arguments you wish to be keyword arguments. Carrying over the test method from last time, it now becomes this.


def test(option1:true, option2:false)
  puts option1, option2
end

First off, you'll note that the hash is gone. You can do as the first attempt above tried to do, simply state the default values for the arguments in the method definition and it all just works. There's no need for a default values hash. All of the arguments are "true" arguments, not hash keys. While the old method of merging with a hash with default values was not horribly hideous, this is much cleaner.

And all this works as you'd expect. If you call test() with no arguments, option 1 and 2 are true and false. If you call test(option1:false), it will change the value of option1 without changing option2. And similarly you can say test(option2:true) without modifying the value of option1.

Once you understand the concept of this feature, it's easy to just run with it. There are only a few things you need to know.

  • All keyword arguments must be at the end of the argument list. You cannot say def test(age:10, name). Just defining this method will give an error. The only exception is for block arguments, those can still go at the end, such as def test(age:10, &block).
  • Keyword arguments don't behave like default values. When calling a method with keyword arguments, you must specify the name for each of the arguments you're referring to. For example, test(a, b=10) can be called like test(1,2). The second argument simply goes to b. But with keyword arguments you must specify the name of the arguments, you cannot simply pass them in the order they're named in the method definition. So to call test(a,b:10) you cannot say test(1,2), you must say test(1,b:2). This decouples argument order from the API, so programmers can rearrange or add to keyword arguments as they please.
  1. About.com
  2. Computing
  3. Ruby
  4. Beginning Ruby
  5. Keyword Arguments

©2014 About.com. All rights reserved.