Home > Enterprise >  Ignore argument missing error in ellipsis function
Ignore argument missing error in ellipsis function

Time:12-22

The following piece of code produces Error: argument is missing, with no default because of the trailing comma from the caller.

new_game <- function(...) {
  list(...)
}

game <- new_game(
  c(1,2,3),
  c(1,2),
  c(3),
  c(2),
  c(1), # the comma here is the culprit
)

Is it possible to circumvent this error by simply ignoring the last "argument"?

I wanted to design my function like this because, similar to how Rust has trailing commas in match statements or structs, or how Go has trailing commas in composite literals, it makes it easier to rearrange, add, remove lines and commit and pull changes.

CodePudding user response:

Turns out there is a whole documentation on ... where one is able to use ...length() and ...elt(x) to access specific parameters. I decided to put everything in a try-catch block - if an error is thrown, it's probably due to the missing argument at the end (note that missing(...elt(length())) does not work).

game <- function(...) {
  tryCatch(
    list(...),
    error = function(e) lapply(1:(...length()-1), function(x) ...elt(x))
  )
}

Alternatively rlang offers list2 specifically for this usecase.

game <- function(...) {
  rlang::list2(...)
}

CodePudding user response:

R is an odd beast because even though the syntax clearly allows trailing commas, it doesn’t just discard/ignore them, as other languages do. Instead, R pretends that a “missing” argument has been passed. As long as nothing touches the missing argument, that’s a-ok. We can even use this situation without dots:

f = function (a, b, c) a   b

Since c is never read, we don’t need to pass it:

f(1, 2)   # works
f(1, 2, ) # works, too

Not very useful, perhaps. But the following also works:

g = function (a, b, c) a   b   if (missing(c)) 0 else c
g(1, 2)    # 3
g(1, 2, )  # 3
g(1, 2, 3) # 6

… unfortunately this doesn’t help us directly to capture ....

Short of using a read-made solution (e.g. rlang::list2), the only real way to capture dots (allowing trailing commas) is to work on unevaluated arguments, and to manually evaluate them (we might instead be tempted to try missing(...elt(...length())); alas R doesn’t accept that).

There are different ways of getting at the unevaluated dot arguments. The simplest way is to use match.call(expand.dots = FALSE)$.... Which leaves us with:

new_game = function (...) {
    args = match.call(expand.dots = FALSE)$...
    nargs = length(args)
    if (identical(args[[nargs]], quote(expr = ))) nargs = nargs - 1L
    lapply(args[seq_len(nargs)], eval, envir = parent.frame())
}
  • Related