I'm writing a function that accepts a variable number of arguments. Moreover, I would like the user to be able to leave some of these arguments as missing.
Consider just the task of turning the ...
into a list of arguments. Here is my first attempt:
f <- function(...) list(...)
This fails:
f(1,,2)
## Error in f(1, , 2) : argument is missing, with no default
I would like the result to be list(1, NULL, 2)
. (I am not worried about distinguishing f(1,,2)
from f(1,NULL,2)
.)
Here is my second attempt:
g <- function(...)
{
args <- match.call()
miss <- vapply(args, identical, NA, quote(expr=))
args[miss] <- list(NULL)
args[[1L]] <- quote(list)
eval.parent(args)
}
This works (sort of):
identical(g(1,,2), list(1, NULL, 2))
## [1] TRUE
However, it fails when forwarding dots:
gg <- function(...) g(...)
identical(gg(1,,2), list(1,NULL,2))
## [1] FALSE
Is there a way to achieve the same results as g
, but in a way that allows forwarding dots and without using match.call()
?
It's tempting to try the following:
h <- function(...)
{
args <- substitute(list(...))
miss <- vapply(args, identical, NA, quote(expr=))
args[miss] <- list(NULL)
eval.parent(args)
}
However, as @lionel points out, there will be issues if you try to forward ...
from another function:
hh <- function(...) h(letters, ...)
local({ a <- "foo"; h(a) })
## Error in eval(expr, p) : object 'a' not found
It seems like you either need the expand.dots
functionality of match.call
, or you need to use a quosure (from the rlang package).
With the rlang package you have the option of ignoring empty arguments when capturing dots:
my_dots <- function(...) {
rlang::dots_list(..., .ignore_empty = "all")
}
my_dots(1, , 2, )
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
If you really need to translate empty arguments as NULL
there is no other choice than quoting the dots and evaluating them after you've converted the missing arguments. Now if you do that with eval(substitute(alist(...))
you will get into trouble if you attempt to forward dots through several function calls. Base R provides no way of evaluating the quoted argument in their original environment.
Luckily it's easy to do by capturing the dots in quosures:
library("purrr") # For convenient map_if()
my_dots <- function(...) {
quos <- rlang::quos(..., .ignore_empty = "none")
quos <- map_if(quos, rlang::quo_is_missing, function(x) NULL)
map(quos, rlang::eval_tidy)
}
my_dots(1, , 2, )
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> NULL
#>
#> [[3]]
#> [1] 2
#>
#> [[4]]
#> NULL
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With