1

Although there are some questions about this topic (e.g. this question), none of them answer my particular questions (as far as I could tell anyway).

Suppose I have a function which depends on a lot of parameters. For demonstration purposes I chose 3 parameters:

myfun <- function(x1, x2, x3){
   some code containing x1, x2, x3
}

Often the input parameters are already contained in a list:

xlist <- list(x1 = 1, x2= 2, x3 = 3)

I want to run myfun with the inputs contained in xlist like this:

myfun(xlist$x1, xlist$x2, xlist$x3)

However this seems like too big of an effort (because of the high number of parameters).

So I decided to modify myfun: instead of all the input parameters. It now gets the whole list as one single input: at the beginning of the code I use attach in order to use the same code as above.

myfun2 <- function(xlist){
  attach(xlist)
  same code as in myfun containing x1, x2, x3
  detach(xlist)
}

I thought that this would be quite a neat solution, but a lot of users advise to not use attach.

What do you think? Are there any arguments to prefer myfun over myfun2?

Thanks in advance.

Mihai Chelaru
  • 7,614
  • 14
  • 45
  • 51
Cettt
  • 11,460
  • 7
  • 35
  • 58
  • 1
    Are you trying to save a small amount of typing or are you trying to make code that is easy to read and understand 6 months from now? If you weren't in a function then I would say that attach was also dangerous because you can have the same variable declared multiple times in the same environment, but in a function you're a bit safer by using the local function environment. – Adam Sampson Jul 24 '18 at 13:32
  • It is more important that the code is easy to read. However, it is quiet straightforward to understand what the components of `xlist` are in my case so I don't think that readability is an issue. But ask me again in 6 months :) – Cettt Jul 24 '18 at 13:36

4 Answers4

4

I think you'd be better off using do.call. do.call will accept a list and convert them to arguments.

myfun <- function(x1, x2, x3){
  x1 + x2 + x3
}

xlist <- list(x1 = 1, x2= 2, x3 = 3)

do.call(myfun, xlist)

This has the benefit of being explicit about what the arguments are, which makes it much easier to reason with the code, maintain it, and debug it.

The place where this gets tricky is if xlist has more values in it than just those required by the function. For example, the following throws an error:

xlist <- list(x1 = 1, x2 = 2, x3 = 3, x4 = 4)

do.call(myfun, xlist)

You can circumvent this by matching arguments with the formals

do.call(myfun, xlist[names(xlist) %in% names(formals(myfun))])

It's still a bit of typing, but if you're talking about 10+ arguments, it's still a lot easier than xlist$x1, xlist$x2, xlist$x3, etc.

LAP gives a useful solution as well, but would be better used to have with outside the call.

with(xlist, myfun(x1, x2, x3))
Benjamin
  • 16,897
  • 6
  • 45
  • 65
  • This is the right answer. If you already have a function that accepts separate arguments, it doesn't make sense to re-write the function to accept a list of arguments alternatively (or instead). Using `do.call` or `with` is much simpler. – Gregor Thomas Jul 24 '18 at 14:53
  • 1
    `do.call` is often annoying as it doesn't support additional arguments, `rlang::invoke` is wrapper that I find very useful. – moodymudskipper Jul 25 '18 at 13:24
0

You could just use with():

xlist <- list(x1 = 1, x2= 2, x3 = 3)

FOO <- function(mylist){
  with(mylist,
       x1+x2+x3
  )
}

> FOO(xlist)
[1] 6

I'm not convinced of this approach, though. The function would depend on the correctly named elements within the list.

LAP
  • 6,605
  • 2
  • 15
  • 28
  • thank you for your answer. But my function is quiet complicated and `x1` `x2` `x3` enter the code multiple times. So I would have to write with in almost every line which makes the code harder to read. – Cettt Jul 24 '18 at 13:32
  • 1
    `with` is a good answer, but don't use it *inside* the function, use it when the function is called. – Gregor Thomas Jul 24 '18 at 14:29
  • You can wrap the `with()` around all the code within your function. You could also use `with()` as @Gregor suggested, like `with(mylist, FOO(x1, x2, x3))`. – LAP Jul 25 '18 at 07:00
0

My approach would be something like this:

testfun <- function (a_list)
{
  args = a_list
  print(args$x1)
  print(args$x2)
  print(args$x3)
}

my_list <- list(x1=2, x2=3, x3=4)

testfun(my_list)

However, you would need to know the names of the parameters within the function.

Perhaps the do.call() function can come into play here.

do.call('fun', list)
DevGin
  • 443
  • 3
  • 12
  • thank you for your answer. But my function is quiet complicated and `x1` `x2` `x3` enter the code multiple times. So I would have to write `mylist$x1` in almost every line which makes the code harder to read. – Cettt Jul 24 '18 at 13:38
0

You could assign the list to the environment of the function:

myfun <- function(xlist) {
  for (i in seq_along(xlist)) {
    assign(names(xlist)[i], xlist[[i]], envir = environment())
  }
# or if you dislike for-loops
# lapply(seq_along(xlist), function(i) assign(names(xlist)[i], xlist[[i]], envir = parent.env(environment())))
  print(paste0(x2, x3)) # do something with x2 and x3
  print(x1 * x3) # do something with x1 and x3
}
myfun(list(x1 = 4, x2 = "dc", x3 = c(3,45,21)))
r.user.05apr
  • 5,356
  • 3
  • 22
  • 39