6.6 ... (dot-dot-dot)

Functions can have a special argument ... (pronounced dot-dot-dot). With it, a function can take any number of additional arguments. In other programming languages, this type of argument is often called varargs (short for variable arguments), and a function that uses it is said to be variadic.

You can also use ... to pass those additional arguments on to another function.

i01 <- function(y, z) {
  list(y = y, z = z)
}

i02 <- function(x, ...) {
  i01(...)
}

str(i02(x = 1, y = 2, z = 3))
#> List of 2
#>  $ y: num 2
#>  $ z: num 3

Using a special form, ..N, it’s possible (but rarely useful) to refer to elements of ... by position:

i03 <- function(...) {
  list(first = ..1, third = ..3)
}
str(i03(1, 2, 3))
#> List of 2
#>  $ first: num 1
#>  $ third: num 3

More useful is list(...), which evaluates the arguments and stores them in a list:

i04 <- function(...) {
  list(...)
}
str(i04(a = 1, b = 2))
#> List of 2
#>  $ a: num 1
#>  $ b: num 2

(See also rlang::list2() to support splicing and to silently ignore trailing commas, and rlang::enquos() to capture unevaluated arguments, the topic of quasiquotation.)

There are two primary uses of ..., both of which we’ll come back to later in the book:

  • If your function takes a function as an argument, you want some way to pass additional arguments to that function. In this example, lapply() uses ... to pass na.rm on to mean():

    x <- list(c(1, 3, NA), c(4, NA, 6))
    str(lapply(x, mean, na.rm = TRUE))
    #> List of 2
    #>  $ : num 2
    #>  $ : num 5

    We’ll come back to this technique in Section 9.2.3.

  • If your function is an S3 generic, you need some way to allow methods to take arbitrary extra arguments. For example, take the print() function. Because there are different options for printing depending on the type of object, there’s no way to pre-specify every possible argument and ... allows individual methods to have different arguments:

    print(factor(letters), max.levels = 4)
    
    print(y ~ x, showEnv = TRUE)

    We’ll come back to this use of ... in Section 13.4.3.

Using ... comes with two downsides:

  • When you use it to pass arguments to another function, you have to carefully explain to the user where those arguments go. This makes it hard to understand what you can do with functions like lapply() and plot().

  • A misspelled argument will not raise an error. This makes it easy for typos to go unnoticed:

    sum(1, 2, NA, na_rm = TRUE)
    #> [1] NA

6.6.1 Exercises

  1. Explain the following results:

    sum(1, 2, 3)
    #> [1] 6
    mean(1, 2, 3)
    #> [1] 1
    
    sum(1, 2, 3, na.omit = TRUE)
    #> [1] 7
    mean(1, 2, 3, na.omit = TRUE)
    #> [1] 1
  2. Explain how to find the documentation for the named arguments in the following function call:

    plot(1:10, col = "red", pch = 20, xlab = "x", col.lab = "blue")

  3. Why does plot(1:10, col = "red") only colour the points, not the axes or labels? Read the source code of plot.default() to find out.