Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

What I want to do:

I would like to pass as an argument a list a functions to apply them to a specific input. And give output name based on those.

A "reproducible" example

input = 1:5

Functions to pass are mean, min

Expected call:

foo(input, something_i_ask_help_for)

Expected output:

list(mean = 3, min = 1)

If it's not perfectly clear, please see my two solutions to have 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 with this method is that we can't call it by doing: foo(1:5, list(mean, min)).

Solution 2: passing functions names as 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 with this method is that we are not really passing the function-object as argument.

My question:

Both ways works, but I not quite sure which one to choose.

Could you help me by:

  • Telling me which one is the best?
  • What are the advantages and defaults of each methods?
  • Is there a another (better) method

If you need any more information, don't hesitate to ask.

Thanks!

like image 882
Emmanuel-Lin Avatar asked Dec 28 '17 16:12

Emmanuel-Lin


People also ask

Can you pass a list as an argument in a function?

You can send any data types of argument to a function (string, number, list, dictionary etc.), and it will be treated as the same data type inside the function.

How do you pass a function as an argument?

We cannot pass the function as an argument to another function. But we can pass the reference of a function as a parameter by using a function pointer. This process is known as call by reference as the function parameter is passed as a pointer that holds the address of arguments.

How do you pass list values to a function?

You have to explicitly copy the list. You can either do that outside the function or inside the function, but you have to do it. The terms "by value" and "by reference" aren't used often in Python, because they tend to be misleading to people coming from other languages.

How do you pass lists to args in Python?

Expand the dictionary with ** While specifying a dictionary with ** as an argument, the key will be extended as an argument name and value as the argument's value. Each and every single element will pass as the keyword arguments. Let's learn more about lists as arguments.

How to pass a function as an argument to another function?

In Python, just like a normal variable, we can pass a user-defined function as an argument to another function. A function that accepts another function as its parameter is called a Higher-order function. Let’s see how we can implement this through Python code. result = foo ('Welcome To AskPython!!')

How to use arguments in a function in Python?

A function is the most useful object in python. You can pass data objects like strings, lists, dictionaries, or set as an input parameter to the function, and in the function, we can perform operations on its and return as an output result. We can arguments inside the function calling parenthesis. The argument values will be comma-separated.

How do you pass a list as an argument in Python?

Passing List as argument It is useful to pass list as an argument to function sometime. By doing so, function gets the direct access to the content of list. We can pass list of names, shop or even complex objects like list of dictionaries. def list_of_names (names): for name in names: message = f"Hello {name}, How are you today?"

How to pass parameters to a function in Python?

Effective Ways to Pass Parameters to Python Functions 1. Positional argument. The order in which arguments are passed matters. Function when called matches the order in which... 2. Keyword argument. The argument passed is name-value pair. The order in which arguments are passed doesn't really... 3. ...


2 Answers

Simplifying solutions in question

The first of the solutions in the question requires that the list be named and the second requires that the functions have names which are passed as character strings. Those two user interfaces could be implemented using the following simplifications. Note that we add an envir argument to foo2 to ensure function name lookup occurs as expected. Of those the first seems cleaner but if the functions were to be used interactively and less typing were desired then the second does do away with having to specify the 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

Alternately 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 base the user interface on passing a formula containing the function names since formulas already incorporate the notion of environment:

foo2b <- function(input, fo) foo2(input, all.vars(fo), envir = environment(fo))
foo2b(1:5, ~ min + max) # test

Optional names while passing function itself

However, the question indicates that it is preferred that

  • the functions themselves be passed
  • names are optional

To incorporate those features the following allows the list to have names or not or a mixture. If a list element does not have a name then the expression defining the function (usually its name) is used.

We can derive the names from the list's names or when a name is missing we can use the function name itself or if the function is anonymous and so given as its definition then the name can be the expression defining the function.

The key is to use match.call and pick it apart. We ensure that funs is a list in case it is specified as a character vector. match.fun will interpret functions and character strings naming functions and look them up in the parent frame so we use a for loop instead of Map or lapply in order that we not 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
like image 138
G. Grothendieck Avatar answered Nov 15 '22 12:11

G. Grothendieck


One thing that you are missing is replacing the for loops with lapply. Also for functional programming it is often good practice to separate functions to do one thing. I personally like the version from solution 1 where you pass the functions in directly because it avoids another call in R and therefore is more efficient. In solution 2, it is best to use match.fun instead of get. match.fun is stricter than get in 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 solution 1. To add to this function, you could add some error handling such as is.numeric for x and is.function for funs.

like image 21
troh Avatar answered Nov 15 '22 14:11

troh