Dealing with ... arguments in a wrapper function, calling multiple functions

Viewed 22

I found a way to get it work, but I am not sure if I took the right approach or if there are easier ways to deal with ... in my scenario.

Consider a few silly functions that update a data.table by reference. Note all is about the wrapper, not about what my test functions actually do. Yes in practice they all update x by reference, just like my examples.

Then consider a wrapper function that has as input the data.table and a vector with functions to call one by one. All these functions rely on x and that is always a data.table. The other arguments differ per function, but all have defined default values.

the functions to play with that need to accessed by the wrapper

change_rep <- function(x, length = 3L, multiplier = 1L) {
  x[, col_4 := paste(rep(col_2, (length * multiplier)), sep = "", collapse = ""), col_2]
}

change_case <- function(x, to = "lower") {
  if (to %in% c("upper", "lower", "title")) {
    func <- paste0("stri_trans_to", to)
    x[, col_5 := match.fun(func)(col_4)]
  } else {
    stop(paste0("check the \"to\" argument, \"", to, "\" is not any of c(\"upper\", \"lower\", \"title\")"), call. = F)
  }
}

Now I want a wrapper that allows me to once supply x, tell which functions to apply using a what argument which is a vector. All optional arguments used by the functions called by the wrapper are in the .... The tricky thing here is that if you call a function with arguments that the function does not know, it throws an error. Below the wrapper I have this far and it seems to work fine, but I wonder if this is the most efficient way to handle this and if perhaps I should include error/warning handling for situations where an argument is provided that is not used in any of the functions. For example lengthhhh = 10L would now be silently ignored, while probably one meant length = 10L that would only be used in one of the functions. I highly commented the wrapper.

change_wrapper <- function(x, what, ...) {
  # define the change_* functions we have available
  supported <- c("case", "rep")
  # check for what values we have no function for and show them in a warning
  if (!identical(setdiff(what, supported), character(0))) {
    warning("Check the \"what\" argument. These unsupported values were found and ignored: \"", paste(setdiff(what, supported), collapse = ", "), "\".", call. = F)
  }
  # ignore those above by removing unsupported ones from "what"
  what <- intersect(what, supported)
  # check if "what" still has values, if not throw an error
  if (identical(what, character(0))) {
    stop("Check the \"what\" argument. At least one of \"", paste(supported, collapse = ", "), "\" must be provided.", call. = F)
  }
  # grab the provided ... arguments, note that x is not in this list!
  args <- list(...)
  # loop over the functions we want to call
  for (w in what) {
    # create the function name to call
    func <- paste("change", w, sep = "_")
    # remember ... can contain arguments that are not supported in all of the functions
    # prevent any "Error in *(...) : unused argument
    arguments <- args[names(args) %in% names(formals(match.fun(func)))]
    # add x as it is not part of ...
    arguments$x = x
    # call the correct function and provide the arguments supported
    do.call(match.fun(func), arguments)
  }
}

data and some examples

library(data.table)
library(stringi)

x <- data.table(
  col_1 = letters[1:10],
  col_2 = c(letters[1:5], LETTERS[1:5]),
  col_3 = 1:10
)

x
#     col_1 col_2 col_3
#  1:     a     a     1
#  2:     b     b     2
#  3:     c     c     3
#  4:     d     d     4
#  5:     e     e     5
#  6:     f     A     6
#  7:     g     B     7
#  8:     h     C     8
#  9:     i     D     9
# 10:     j     E    10

change_wrapper(x, what = c("rep", "case", "FOO"), to = "title", length = 2L, multiplier = 2L, blalalala = F)
# Warning message:
# Check the "what" argument. These unsupported values were found and ignored: "FOO". 

x[]
#     col_1 col_2 col_3 col_4 col_5
#  1:     a     a     1  aaaa  Aaaa
#  2:     b     b     2  bbbb  Bbbb
#  3:     c     c     3  cccc  Cccc
#  4:     d     d     4  dddd  Dddd
#  5:     e     e     5  eeee  Eeee
#  6:     f     A     6  AAAA  Aaaa
#  7:     g     B     7  BBBB  Bbbb
#  8:     h     C     8  CCCC  Cccc
#  9:     i     D     9  DDDD  Dddd
# 10:     j     E    10  EEEE  Eeee

change_wrapper(x, what = c("BOO", "FOO"))
# Error: Check the "what" argument. At least one of "case, rep" must be provided.
# In addition: Warning message:
# Check the "what" argument. These unsupported values were found and ignored: "BOO, FOO". 
0 Answers
Related