rassign

R: attach within function


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


Solution

  • 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, and

    • To immediately follow the attach call by an on.exit call to detach 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