Home > Software engineering >  Passing optional arguments with `...` to multiple functions; control argument matching?
Passing optional arguments with `...` to multiple functions; control argument matching?

Time:04-12

I'm trying to write a parent function that calls a bunch of sub-functions that all have pretty sensible defaults and are well documented. Based on the value of one parameter, there are potentially different arguments I'd like to pass down to different sub-functions for customization. Is there a way to pass arguments to multiple functions using elipsis or another strategy?

Here's a simple example; here the challenge is being able to pass na.rm and/or base when a user wants, but otherwise use the existing defaults:

dat <- c(NA, 1:5)
# I want a flexible function that uses sensible defaults unless otherwise specified
meanLog<-function(x, ...){
  y <- log(x, ...)
  z <- mean(y, ...)
  return(z)
}

# I know I can pass ... to one function wrapped inside this one. 
justLog <- function(x, ...){
  log(x, ...)
}

justLog(dat)
justLog(dat, base = 2)

# or another
justMean <- function(x, ...){
  mean(x, ...)
}

justMean(dat)
justMean(dat, na.rm =T)


# but I can't pass both arguments
meanLog(dat) # works fine, but I want to customize a few things
meanLog(dat, na.rm =T, base = 2)

justMean(dat, base =2)
# In this case that is because justLog breaks if it gets an unused na.rm 
justLog(dat, na.rm =T)

CodePudding user response:

1) Define do.call2 which is like do.call except that it accepts unnamed arguments as well as named argument in the character vector accepted which defaults to the formals in the function.

Note that the arguments of mean do not include na.rm -- it is slurped up by the dot dot dot argument -- but the mean.default method does. Also primitive functions do not have formals so the accepted argument must be specified explicitly for those rather than defaulted.

do.call2 <- function(what, args, accepted = formalArgs(what)) {
  ok <- names(args) %in% c("", accepted)
  do.call(what, args[ok])
}

# test

dat <- c(NA, 1:5)
meanLog <- function(x, ...){
  y <- do.call2("log", list(x, ...), "base")
  z <- do.call2("mean.default", list(y, ...))
  return(z)
}

meanLog(dat, na.rm = TRUE, base = 2)
## [1] 1.381378

# check

mean(log(dat, base = 2), na.rm = TRUE)
## [1] 1.381378

2) Another possibility is to provide separate arguments for mean and log.

(A variation of that is to use dot dot dot for one of the functions and argument lists for the others. For example nls in R uses dot dot dot but also uses a control argument to specify other arguments.)

# test

dat <- c(NA, 1:5)
meanLog <- function(x, logArgs = list(), meanArgs = list()) {
  y <- do.call("log", c(list(x), logArgs))
  z <- do.call("mean", c(list(y), meanArgs))
  return(z)
}

meanLog(dat, logArgs = list(base = 2), meanArgs = list(na.rm = TRUE))
## [1] 1.381378

# check

mean(log(dat, base = 2), na.rm = TRUE)
## [1] 1.381378
  • Related