list2(...)
is equivalent to list(...)
with a few additional
features, collectively called dynamic dots. While
list2()
hard-code these features, dots_list()
is a lower-level
version that offers more control.
list2(...) dots_list( ..., .named = FALSE, .ignore_empty = c("trailing", "none", "all"), .preserve_empty = FALSE, .homonyms = c("keep", "first", "last", "error"), .check_assign = FALSE )
... | Arguments to collect in a list. These dots are dynamic. |
---|---|
.named | Whether to ensure all dots are named. Unnamed
elements are processed with |
.ignore_empty | Whether to ignore empty arguments. Can be one
of |
.preserve_empty | Whether to preserve the empty arguments that
were not ignored. If |
.homonyms | How to treat arguments with the same name. The
default, |
.check_assign | Whether to check for |
A list containing the ...
inputs.
# Let's create a function that takes a variable number of arguments: numeric <- function(...) { dots <- list2(...) num <- as.numeric(dots) set_names(num, names(dots)) } numeric(1, 2, 3) #> [1] 1 2 3 # The main difference with list(...) is that list2(...) enables # the `!!!` syntax to splice lists: x <- list(2, 3) numeric(1, !!! x, 4) #> [1] 1 2 3 4 # As well as unquoting of names: nm <- "yup!" numeric(!!nm := 1) #> yup! #> 1 # One useful application of splicing is to work around exact and # partial matching of arguments. Let's create a function taking # named arguments and dots: fn <- function(data, ...) { list2(...) } # You normally cannot pass an argument named `data` through the dots # as it will match `fn`'s `data` argument. The splicing syntax # provides a workaround: fn("wrong!", data = letters) # exact matching of `data` #> [[1]] #> [1] "wrong!" #> fn("wrong!", dat = letters) # partial matching of `data` #> [[1]] #> [1] "wrong!" #> fn(some_data, !!!list(data = letters)) # no matching #> $data #> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" #> [20] "t" "u" "v" "w" "x" "y" "z" #> # Empty arguments trigger an error by default: try(fn(, )) #> list() # You can choose to preserve empty arguments instead: list3 <- function(...) dots_list(..., .preserve_empty = TRUE) # Note how the last empty argument is still ignored because # `.ignore_empty` defaults to "trailing": list3(, ) #> [[1]] #> #> # The list with preserved empty arguments is equivalent to: list(missing_arg()) #> [[1]] #> #> # Arguments with duplicated names are kept by default: list2(a = 1, a = 2, b = 3, b = 4, 5, 6) #> $a #> [1] 1 #> #> $a #> [1] 2 #> #> $b #> [1] 3 #> #> $b #> [1] 4 #> #> [[5]] #> [1] 5 #> #> [[6]] #> [1] 6 #> # Use the `.homonyms` argument to keep only the first of these: dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "first") #> $a #> [1] 1 #> #> $b #> [1] 3 #> #> [[3]] #> [1] 5 #> #> [[4]] #> [1] 6 #> # Or the last: dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "last") #> $a #> [1] 2 #> #> $b #> [1] 4 #> #> [[3]] #> [1] 5 #> #> [[4]] #> [1] 6 #> # Or raise an informative error: try(dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "error")) #> Error : Arguments can't have the same name. We found these problems: #> * Multiple arguments named `a` at positions 1 and 2 #> * Multiple arguments named `b` at positions 3 and 4 # dots_list() can be configured to warn when a `<-` call is # detected: my_list <- function(...) dots_list(..., .check_assign = TRUE) my_list(a <- 1) #> Warning: Using `<-` as argument is often a mistake. #> Do you need to use `=` to match an argument? #> #> If you really want to use `<-`, please wrap in braces: #> #> # Bad: #> fn(a <- 1) #> #> # Good: #> fn(a = 1) # Match 1 to parameter `a` #> fn({ a <- 1 }) # Assign 1 to variable `a` #> [[1]] #> [1] 1 #> # There is no warning if the assignment is wrapped in braces. # This requires users to be explicit about their intent: my_list({ a <- 1 }) #> [[1]] #> [1] 1 #>