Video

Want to see the full-length video right now for free?

Notes

What are keyword arguments?

Keyword arguments are a feature in Ruby 2.0 and higher. They're an alternative to positional arguments, and are really similar (conceptually) to passing a hash to a function, but with better and more explicit errors.

Here's an example:

def foo(bar: "default")
  puts bar
end

foo # prints "default"
foo(bar: "baz") # prints "baz"

The foo function has a keyword argument named bar. In this case, we also provide a default value for the bar keyword. When we're calling foo, it looks a lot like we're passing a hash like { bar: "baz" }, but we're actually passing a keyword argument.

For a method with a single argument, this looks like it might not matter that much, but being forced to write the name of the parameter when it's called makes it easier to read and understand the calling code.

How to use keyword arguments

Boolean values are probably the best usage of keyword arguments, because they make code like this (which has a [Control Couple][cc]):

[cc]: https://github.com/troessner/reek/blob/master/docs/Control-Couple.md

render_video video, false

into this much more readable code:

render_video video, subscriber: false

Keyword arguments can be used with positional arguments, though they have to come after the positional arguments:

# This is the correct version.
def render_video(video, has_access: true, subscriber: false)
  # method body goes here
end

# This version is wrong! It raises a syntax error because positional arguments
# are after keyword arguments.
# def render_video(has_access: true, subscriber: false, video)
#   # method body goes here
# end

By using positional arguments, we can pass has_access or subscriber, and we don't have to pass both (though we can). If we had positional arguments with default values, we have less flexibility. Additionally by using keyword arguments, we can get a less visually noisy way to take arguments.

Required keyword arguments

In Ruby 2.1, required keyword arguments were added. In Ruby 2.0, keyword arguments must have default values.

Here's what required keyword arguments look like:

def render_video(video, has_access:, subscriber: false)
  # method body goes here
end

Note that has_access doesn't have a default value, but is still required. We have more flexibility than positional arguments, where arguments with default values must come after arguments without defaults. With keyword arguments, we can move the keyword arguments around in the method definition without changing how it's called or how the method is implemented:

def render_video(video, subscriber: false, has_access:)
  # method body goes here
end

Tradeoffs

We have to type a little more code in order to use keyword arguments, but that tradeoff is almost always worth it because our code is more explicit.

Keyword arguments are better than using a hash because we get better errors. Getting a key from a Hash can fail silently (unless we use Hash#fetch), while required keyword arguments will never fail silently. Plus, even if we do use Hash#fetch, the error messages from keyword arguments are better and help us track down exactly what's going on.

Refactoring with keyword arguments

Refactor positional -> keyword

Here's some code with positional arguments:

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

total(100, 10, 5) # => 105

These arguments are hard to understand and hard to use correctly -- we might accidentally switch the tax and discount when calling total, and we wouldn't see any errors.

Let's refactor it so we can see what these parameters mean:

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

total(subtotal: 100, tax: 10, discount: 5) # => 105

Now it's clear, even at the calling site, what each argument means. And position no longer matters, so we could do this:

total(tax: 10, subtotal: 100, discount: 5) # => 105

Benefits

With positional arguments, position matters. If we remove one or move it around, the meaning changes. Keyword arguments don't care about position, which makes adding a new argument easy, especially if it has a default value (since callers don't have to change).

Keyword arguments also go well with positional arguments. One pattern thoughtbot uses is a positional argument as the first, "main" argument, with "secondary" arguments as keyword arguments after it.

Further reading