rank < function(x,
ties.method =
c("average", "first", "last", "random", "max", "min")
) {
ties.method < match.arg(ties.method)
switch(ties.method,
average = ,
min = ,
max = .Internal(rank(x, length(x), ties.method)),
first = sort.list(sort.list(x)),
last = sort.list(rev.default(sort.list(x, decreasing = TRUE))),
random = sort.list(order(x, stats::runif(length(x))))
)
}
x < c(1, 2, 2, 3, 3, 3)
rank(x)
#> [1] 1.0 2.5 2.5 5.0 5.0 5.0
rank(x, ties.method = "first")
#> [1] 1 2 3 4 5 6
rank(x, ties.method = "min")
#> [1] 1 2 2 4 4 4
10 Enumerate possible options
10.1 What’s the pattern?
If the possible values of an argument are a small set of strings, set the default argument to the set of possible values, and then use match.arg()
or rlang::arg_match()
in the function body. This convention advertises to the knowledgeable user^{1} what the possible values, and makes it easy to generate an informative error message for inappropriate inputs. This interface is often coupled with an implementation that uses switch()
.
This convention makes it possible to advertise the possible set of values for an argument. The advertisement happens in the function specification, so you see in tool tips and autocomplete, without having to look at the documentation.
10.2 What are some examples?
In
difftime()
,units
can be any one of “auto”, “secs”, “mins”, “hours”, “days”, or “weeks”.In
format()
,justify
can be “left”, “right”, “center”, or “none”.In
trimws()
, you can choosewhich
side to remove whitespace from: “both”, “left”, or “right”.In
rank()
, you can select theties.method
from one of “average”, “first”, “last”, “random”, “max”, or “min”.
10.3 How do I use it?
To use this technique, set the default value to a character vector, where the first value is the default. Inside the function, use match.arg()
or rlang::arg_match()
to check that the value comes from the known good set, and pick the default if none is suppled.
Take rank()
, for example. The heart of its implementation looks like this:
Note that match.arg()
will automatically throw an error if the value is not in the set:
rank(x, ties.method = "middle")
#> Error in match.arg(ties.method): 'arg' should be one of "average", "first", "last", "random", "max", "min"
It also supports partial matching so that the following code is shorthand for ties.method = "random"
:
rank(x, ties.method = "r")
#> [1] 1 2 3 6 5 4
We prefer to avoid partial matching because while it saves a little time writing the code, it makes reading the code less clear. rlang::arg_match()
is an alternative to match.arg()
that doesn’t support partial matching. It instead provides a helpful error message:
rank2 < function(x, ties.method = c("average", "first", "last", "random", "max", "min")) {
ties.method < rlang::arg_match(ties.method)
rank(x, ties.method = ties.method)
}
rank2(x, ties.method = "r")
#> Error in `rank2()`:
#> ! `ties.method` must be one of "average", "first", "last", "random",
#> "max", or "min", not "r".
#> ℹ Did you mean "random"?
# It also provides a suggestion if you misspell the argument
rank2(x, ties.method = "avarage")
#> Error in `rank2()`:
#> ! `ties.method` must be one of "average", "first", "last", "random",
#> "max", or "min", not "avarage".
#> ℹ Did you mean "average"?
10.3.1 How keep defaults short?
This technique is a best used when the set of possible values is short as otherwise you run the risk of dominating the function spec with this one argument (Chapter 9). If you have a long list of possibilities, there are three possible solutions:

Set a single default and supply the possible values to
match.arg()
/arg_match()
:rank2 < function(x, ties.method = "average") { ties.method < arg_match( ties.method, c("average", "first", "last", "random", "max", "min") ) }

If the values are used by many functions, you can store the options in an exported vector:
ties.methods < c("average", "first", "last", "random", "max", "min") rank2 < function(x, ties.method = ties.methods) { ties.method < arg_match(ties.method) }
For example
stats::p.adjust()
,stats::pairwise.prop.test()
,stats::pairwise.t.test()
,stats::pairwise.wilcox.test()
all usep.adjust.method = p.adjust.methods
. 
You can store the options in a exported named list^{2}. That has the advantage that you can advertise both the source of the values, and the defaults, and the user gets a nice autocomplete of the possible values.