Ruby 2 Keyword Arguments

With Ruby 2.0 we now have first-class support for keyword arguments in our method parameters. (“First class” means it is a fundamental part of the language and doesn’t require extensions to make work.)

def foo(name: 'default')
  puts name
end

foo # => 'default'
foo(name: 'something') # => 'something'

In Ruby 1.9, we could do something similar using a single Hash parameter:

def foo(options = {})
  name = options.fetch(:name, 'default')
  puts name
end

foo # => 'default'
foo(name: 'something') # => 'something'

Ruby 2.0 blocks can also be defined with keyword arguments:

define_method(:foo) do |name: 'default'|
  puts name
end

foo # => 'default'
foo(name: 'something') # => 'something'

To achieve a similar behavior in Ruby 1.9, the block would have had to take an options hash, from which we would then extract argument values.

Required keyword arguments

Ruby 2.1 introduced required keyword arguments, which are defined with a trailing colon. (Although keyword arguments are available in Ruby 2.0, it wasn’t until Ruby 2.1 that you could specify required and non-requir)

def foo(name:)
  puts name
end

foo # => ArgumentError: missing keyword: bar
foo(name: 'something') # => 'something'

If a required keyword argument is missing, Ruby will raise an ArgumentError which tells us specifically which required argument the calling code forgot to include.

Keyword arguments vs. options hash

With keyword arguments defined in the method signature itself, we can immediately discover the names of the arguments without having to read the body of the method.

With “first-class” keyword arguments now in Ruby, we don’t have to write the “boilerplate” code to extract hash options.

Note that “the calling code” is syntactically equal to calling a method with hash arguments. This makes for an easy transition from options hashes (pre Ruby 2.0) to keyword arguments (Ruby 2.0).

Keyword arguments vs positional arguments

Assume we have a method with positional arguments:

def strange_total(subtotal, tax, discount)
  subtotal + tax - discount
end

strange_total(200, 50, 5) # => 245

This method does its job, but as the next coder who comes across strange_total, the arity (positional arguments) of the arguments makes it impossible to read right away what the numbers 200, 50, and 5 signify.

By using keyword arguments, the next developer will know what the arguments mean without looking up the implementation of the called method.

def obvious_total(subtotal:, tax:, discount:)
  subtotal + tax - discount
end

obvious_total(subtotal: 200, tax: 50, discount: 5) # => 245

Keyword arguments allow us to switch the order of the arguments. When we do, we do not affect the behavior of the method:

obvious_total(subtotal: 100, discount: 5, tax: 10) # => 145

If we switch the order of the positional arguments, we are not going to get the same results.

mysterious_total(200, 5, 50) # => 155

What is connascense?

Connascence between two software components A and B means either 1) that you can postulate some change to A that would require B to be changed (or at least carefully checked) in order to preserve overall correctness, or 2) that you can postulate some change that would require both A and B to be changed together in order to preserve overall correctness.

– Meilir Page-Jones, What Every Programmer Should Know About Object-Oriented Design

When one Ruby method has to know the order of another method’s positional arguments, we end up with a connascence of position.

That means we’ll need to change all callers of that method accordingly if we ever change the method arguments (Expensive cost of change.)

In addition, our mental model of how to use this method must change as well.

Keyword arguments have trade-offs. Positional arguments offer a more succinct way to call a method, especially if that method takes only one or two arguments.

The code clarity and maintainability gained from keyword arguments generally outweigh the brevity offered by positional arguments.

If you can easily guess their meanings based on the method’s name and there are only one or two arguments, positional arguments are still fine.

But generally speaking, codebases written after Ruby 2.0 should prefer keyword arguments.