Want to see the full-length video right now for free?
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.
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.
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
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.
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
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.