Put after required arguments

What’s the pattern?

If you use in a function, put it after the required arguments and before the optional arguments.

This has two positive impacts:

  • It forces the user of your function to fully name optional arguments, because arguments that come after ... are never matched by position or by partial name. We believe that using full names for optional arguments is good practice because it makes code easier to read.

  • This in turn means that uou can easily add new optional arguments or change the order of existing arguments without affecting existing code.

What are some examples?

The arguments to mean() are x, trim, na.rm and . This means that you can write code like this:

x <- c(1, 2, 10, NA)
mean(x, , TRUE)
#> [1] 4.333333
mean(x, n = TRUE, t = 0.1)
#> [1] 4.333333

Not only does this allow for confusing code1, it also makes it hard to later change the order of these arguments, or introduce new arguments that might be more important.

If mean() instead placed before trim and na.rm, like mean2()2 below, then you must fully name each argument:

mean2 <- function(x, ..., na.rm = FALSE, trim = 0) {
  mean(x, ..., na.rm = na.rm, trim = trim)
}

mean2(x, na.rm = TRUE)
#> [1] 4.333333
mean2(x, na.rm = TRUE, trim = 0.1)
#> [1] 4.333333

How do I remediate past mistakes?

It’s straightforward to fix a function where you’ve put ... in the wrong place: you just need to change the argument order and use rlang::check_dots_used() to check that no arguments are lost (learn more in Chapter 25). This is a breaking change, but it tends to affect relatively little code because most people do fully name optional arguments.

We can use this approach to make a safer version of mean():

mean3 <- function(x, ..., na.rm = FALSE, trim = 0) {
  rlang::check_dots_used()
  mean(x, ..., na.rm = na.rm, trim = trim)
}

mean3(x, , TRUE)
#> Error in `mean3()`:
#> ! Arguments in `...` must be used.
#> ✖ Problematic argument:
#> • ..1 = TRUE
#> ℹ Did you misspell an argument name?

mean3(x, n = TRUE, t = 0.1)
#> Error in `mean3()`:
#> ! Arguments in `...` must be used.
#> ✖ Problematic arguments:
#> • n = TRUE
#> • t = 0.1
#> ℹ Did you misspell an argument name?

In base R you can use base::chkDots(), but it uses a slightly simpler technique which means it’s not suitable for usage in S3 methods.

See also

  • Chapter 23: if is a required argument because it’s used to combine an arbitrary number of objects in a data structure.
  • Chapter 25: to ensure that arguments to never go silently missing.

  1. As much as we recommended people don’t write code like this, you know someone will!↩︎

  2. Note that I moved na.rm = TRUE in front of trim because I believe na.rm is the more important argument because it’s used vastly more often than trim and I’m following Chapter 6.↩︎