I want to pass parameters to a function using a list in R. I believe the general consensus is to never use attach()
. However, it seems to make sense when the goal is to pass a list to a function and attach it as control parameters within the function environment. This is similar to how the parameter list control
is used in optim
.
An example:
opts = list(N = 50, m = c(0,0,0), V = diag(3^2-0.1,3)+0.1)
a.fun = function(opts){
attach(opts)
# N = opts$N; m = opts$m; V = opts$V # isn't this just extra typing?
# mapply(function(a,b) assign(a,b),names(opts), opts)
X = MASS::mvrnorm(N, m, V)
if( all(m == 0 ) ) VX = 1/(N-1)*t(X) %*% X else VX = var(X)
return(VX)
}
a.fun(opts)
But R complains about objects being masked. So apparently there is no such thing as a local search path.
Is there a better way to do this? I can call mapply
with assign, but this seems like overkill. It also gets complicated if I want to pass a nested list of parameters.
What are the preferred approaches to assigning parameters within a function if you don't want to make all of them explicit arguments?
Thanks
From the manual ?attach, under "Good practice":
Good practice
attach
has the side effect of altering the search path and this can easily lead to the wrong object of a particular name being found. ...In programming, functions should not change the search path unless that is their purpose. Often
with
can be used within a function. If not, good practice is to
Always use a distinctive
name
argument, andTo immediately follow the attach call by an
on.exit
call todetach
using the distinctive name.This ensures that the search path is left unchanged even if the function is interrupted or if code after the
attach
call changes the search path.
Therefore if we insist on using attach within a function, its recommended use should be like this:
N = -1
opts = list(N = 50, m = c(0,0,0), V = diag(3^2-0.1,3)+0.1)
opts2 = list(N = 10, m = NA, V = NA)
attach(opts2)
a.fun = function(opts){
on.exit(
expr = {
detach(rnd.name, character.only = TRUE) } )
rnd.name = basename(tempfile())
assign(x = rnd.name, value = opts)
attach(get(rnd.name), name = rnd.name, warn.conflicts = FALSE)
print(search());
print(paste("N =", N))
X = MASS::mvrnorm(N, m, V)
if( all(m == 0 ) ) VX = 1/(N-1)*t(X) %*% X else VX = var(X)
return(VX)
}
a.fun(opts)
search()
detach(opts2)
However, note that N=-1
in the .GlobalEnv
masks both N=10
from the attached opt2
, as well as N=50
from opt
within the function environment. Attaching from within the function environment, or anywhere else, only ever affects the global search path, for which .GlobalEnv
always has the priority.
This is why attaching is dangerous and not recommended. Hence the recommendation to use with({})
. Nevertheless, with
creates nested inelegant code. All that is really needed is a quick way to intake the opt list and use it within the function.
The way to do that is:
list2env(opts, envir = environment())
So finally we have a convenient way to assign list elements by name for use within a function:
assign.lst_fun = function(opts){
list2env(opts, envir = environment())
print(ls(environment()))
print(paste("N =", N))
X = MASS::mvrnorm(N, m, V)
if( all(m == 0 ) ) VX = 1/(N-1)*t(X) %*% X else VX = var(X)
return(VX)
}
assign.lst_fun(opts)
N