Simplification of the considered decisions
The first solution in the question requires that the list be named, and the second requires that the functions have names that are passed as character strings. These two user interfaces can be implemented using the following simplifications. Note that we are adding the envir argument to foo2 to ensure that the function name lookup occurs as expected. Of these, the former seems cleaner, but if functions are to be used interactively, but are less typed, then the latter will end the need to specify names.
foo1 <- function(input, funs) Map(function(f) f(input), funs) foo1(1:5, list(min = min, max = max)) # test foo2 <- function(input, nms, envir = parent.frame()) { Map(function(nm) do.call(nm, list(input), envir = envir), setNames(nms, nms)) } foo2(1:5, list("min", "max")) # test
Alternatively, we could build foo2 on foo1 :
foo2a <- function(input, funs, envir = parent.frame()) { foo1(input, mget(unlist(funs), envir = envir, inherit = TRUE)) } foo2a(1:5, list("min", "max"))
or establish a user interface when transferring a formula containing function names, because the formulas already include the concept of an environment:
foo2b <- function(input, fo) foo2(input, all.vars(fo), envir = environment(fo)) foo2b(1:5, ~ min + max) # test
Additional names when passing the function itself
However, the question indicates that it is preferable that
- the functions themselves are passed
- names are optional
To enable these features, the following allows the list to have names either not or a mixture. If the list item does not have a name, an expression is used that defines the function (usually its name).
We can get the names from the names of the lists, or when there is no name, we can use the name of the function on our own or if the function is anonymous and therefore defined as its definition, then this name can be an expression that defines the function.
The key is to use match.call and split it. We guarantee that funs is a list if it is specified as a character vector. match.fun will interpret the function naming functions and character strings and see them in the parent frame, so we use a for loop instead of Map or lapply so that we don't generate a new function scope.
foo3 <- function(input, funs) { cl <- match.call()[[3]][-1] nms <- names(cl) if (is.null(nms)) nms <- as.character(cl) else nms[nms == ""] <- as.character(cl)[nms == ""] funs <- as.list(funs) for(i in seq_along(funs)) funs[[i]] <- match.fun(funs[[i]])(input) setNames(funs, nms) } foo3(1:5, list(mean = mean, min = "min", sd, function(x) x^2))
giving:
$mean [1] 3 $min [1] 1 $sd [1] 1.581139 $`function(x) x^2` [1] 1 4 9 16 25