What is the best way to pass a list of functions as an argument?

I am looking for advice on the best way to code that passes a list of functions as an argument.

What I want to do:

I would like to pass as an argument a list of functions to apply them to a specific input. And give a name for the output based on this data.

Example of "reproducible"

input = 1:5 

Transmission Functions: mean , min

Expected Call:

 foo(input, something_i_ask_help_for) 

Expected Result:

 list(mean = 3, min = 1) 

If this is not entirely clear, see my two solutions for an illustration.

Solution 1: Passing functions as arguments

 foo <- function(input, funs){ # Initialize output output = list() # Compute output for (fun_name in names(funs)){ # For each function I calculate it and store it in output output[fun_name] = funs[[fun_name]](input) } return(output) } foo(1:5, list(mean=mean, min=min)) 

What I don't like about this method is that we cannot call it: foo(1:5, list(mean, min)) .

Solution 2: passing function names as an argument and using get

 foo2 <- function(input, funs){ # Initialize output output = list() # Compute output for (fun in funs){ # For each function I calculate it and store it in output output[fun] = get(fun)(input) } return(output) } foo2(1:5, c("mean", "min")) 

What I don't like about this method is that we do not pass the object object as an argument.

My question is:

Both methods work, but I'm not quite sure which one to choose.

Could you help me:

  • Tell me which one is better?
  • What are the benefits and default values ​​for each method?
  • Is there any other (best) method

If you need more information, feel free to ask.

Thanks!

+5
source share
2 answers

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")) # test 

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 
+3
source

One thing you are missing is replacing for loops with lapply . It is also often good practice for functional programming to separate functions in order to do one thing. I personally like the version from solution 1, in which you pass the function directly, because it avoids another call to R and, therefore, is more efficient. In solution 2, it is better to use match.fun instead of get . match.fun more strict than get when searching for functions.

 x <- 1:5 foo <- function(input, funs) { lapply(funs, function(fun) fun(input)) } foo(x, c(mean=mean, min=min)) 

The above code simplifies your decision 1. To add to this function, you can add some error handling, for example is.numeric for x and is.function for fun.

+1
source

Source: https://habr.com/ru/post/1274370/


All Articles