Dots (...) vs arg lists for function 'forwarding'

R's dots (i.e. ellipsis) are great for wring general-purpose wrapper functions, e.g.:

wrapper_f <- function(x, ...) {
  x <- some_transform(x)
  inner_f(x, ...)
}

But when your wrapper wraps more than one inner function, the dots args can lead to confusion since some of the wrong (or at least un-expected) additional arguments are sent to each inner function:

wrapper_f <- function(x, ...) {
  x <- some_transform(x)
  x <- inner_f1(x, ...)
  inner_f2(x, ...)
}

So when this issue comes around, I usually end up modifying the wrapper function to take not dots, but rather separate lists for args to pass to the inner functions, like so:

wrapper_f <- function(x, inner_f1_args = list(), inner_f2_args = list()) {
  x <- some_transform(x)
  x <- do.call(inner_f1, c(list(x = x), inner_f1_args))
  do.call(inner_f2, c(list(x = x), inner_f2_args))
}

This works, but mixing the dots paradigm with this 'list' paradigm (just when the wrapper function happens to wrap one vs many underlying functions) is somehow inelegant to me. How do the rest of you handle this nicely? Perhaps some rlang patterns of which I'm not yet aware?

2 Likes

I admit to not knowing of a more elegant way of dealing with this, and will be very pleased if something new comes my way. I've typically employed lists in these situations.

One other option that isn't particularly difficult to implement is to match the elements in ... to known argument names in your functions. The obvious downside is that the elements to ... must be named in this case. Something like the following should works and imposes the restriction that each element in ... be named.

fn1 <- function(a, b, c){
  a + b + c
}

fn2 <- function(x, y, z){
  x - y - z
}

match_from_dots <- function(dots, fn){
  arg <- match(names(formals(fn)), names(dots))
  dots[arg[!is.na(arg)]]
}

wrap <- function(...){
  dots <- list(...)
  
  checkmate::assert_named(dots)

  list(
    fn1 = do.call("fn1", match_from_dots(dots, fn1)),
    fn2 = do.call("fn2", match_from_dots(dots, fn2))
  )
}

wrap(a = 1, x = 2, c = 3, b = 2, z = 3,  y = 1)
1 Like