Let a function within a function extract arguments it expects from the ellipses and disregard all others?

Advanced R gives a simple example of "dot dot dot" and how to pass named arguments through to a function within a 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

This approach only works when the inner function accepts all the arguments provided via the ellipses. For example, adding an argument that the inner function doesn't use causes an error:

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

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

# Errors since i01() receives j, which it doesn't know how to handle
str(i02(x = 1, y = 2, z = 3, j = 4))
# Error in i01(...) : unused argument (j = 4)

I found a way around this that gets very long-winded in real world uses (e.g. imagine with > dozen arguments):

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

i02 <- function(x, ...) {
  ellip <- list(...)
  i01(
    if(!is.null(ellip$y)){ ellip$y },
    if(!is.null(ellip$z)){ ellip$z }
  )
}

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

Question

Is there an elegant way to tell a function within a function to i) look at ... and ii) extract everything it recognises and ignore everything else?


I wouldn't call this elegant, but here's one way to do it:

i02 <- function(x, ...) {
   i01args <- list(...)[c("y", "z")]
   do.call(i01, i01args)
}

This assumes that you will always include both y and z in the .... If they are optional, you could do something like

i02 <- function(x, ...) {
   allargs <- list(...)
   i01args <- allargs[which(names(allargs) %in% c("y", "z"))]
   do.call(i01, i01args)
}

This version loses unnamed arguments.

Generally speaking I try to avoid this kind of construction. You sometimes need to do it when you want to pass optional arguments to two different functions, e.g. i02 calls both i01 and i03. But then it depends on the names of the arguments for those two functions being completely distinct. If both i01 and i03 have an argument named w, how would you know which one of them should receive it? A better construction is something like

i02 <- function(x, i01args = list(), i03args = list()) {
  do.call(i01, i01args)
  do.call(i03, i03args)
}

Here are three approaches. The last one is a generalization of the second.

Either take y and z out of ... as in io2a or without modifying the arguments as in io2b or if io2 doesn't know the arguments or io1 and must find them out dynamically construct the call as in io2c.

Note that the use of list(...) in the question has the disadvantage that it evaluates all the arguments including the ones that are not used whereas the approach here does not. Look at lm source code for another example of this.

io2a <- function(x, y, z, ...) i01(y, z)
io2a(x = 1, y = 2, z = 3, j = 4) |> str()
## List of 2
##  $ y: num 2
##  $ z: num 3

io2b <- function(x, ...) {
  mf <- match.call()
  m <- match(c("y", "z"), names(mf), 0L)
  mf <- mf[c(1L, m)]
  mf[[1L]] <- as.name("i01")
  eval.parent(mf)
}
io2b(x = 1, y = 2, z = 3, j = 4) |> str()
## List of 2
##  $ y: num 2
##  $ z: num 3

# same except we dynamically determine y and z
io2c <- function(x, ...) {
  mf <- match.call()
  nms <- intersect(names(mf), names(formals(i01)))
  m <- match(nms, names(mf), 0L)
  mf <- mf[c(1L, m)]
  mf[[1L]] <- as.name("i01")
  eval.parent(mf)
}
io2c(x = 1, y = 2, z = 3, j = 4) |> str()
## List of 2
##  $ y: num 2
##  $ z: num 3