Search code examples
rnamespacesenvironment

In R, how do I evaluate an expression in a specific environment within a function?


In my apparent naievety, I assumed that when one calls eval and specifies an environment (envir), the expression (expr) is evaluated in that environment.

However :-)

This works as expected:

xx <- 10
nn <- 20
exprs <- binom.test(x=xx,n=nn)
eval(exprs);

By default, eval evaluates in parent.frame(), which the help reveals is [t]he parent frame of a function evaluation is the environment in which the function was called.

So in the above example, this is the global environment, where indeed, xx and nn are defined. So far, so good.

newEnv <- new.env();
assign('xxx', 10, envir = newEnv);
assign('nnn', 30, envir = newEnv);
exprs2 <- expression(binom.test(x=xxx,n=nnn));
eval(exprs2, envir=newEnv);

This also works as expected; xxx and nnn are defined in the newEnv environment, and binom.test is evaluated in that environment.

Now we wrap this in a function (the one I'm trying to build - I'm building it using the pwr package, but for this example I'm using binom.test because it's base R and it still doesn't work :-)

loopFunction <- function(expr,
                         ...) {

  ### Get all 'dots' in a named list
  arguments <- list(...);
  argNames <- names(arguments);

  if (any(length(tail(arguments, -2) > 1))) {
    stop("Only the first two arguments may have length > 1!");
  }

  for (esIndex in seq_along(arguments[[1]])) {
    for (pwrIndex in seq_along(arguments[[2]])) {
      tempEnvironment <-
        new.env();
      assign(argNames[1], arguments[[1]][esIndex],
             envir = tempEnvironment);
      assign(argNames[2], arguments[[2]][pwrIndex],
             envir = tempEnvironment);
      if (length(arguments) > 2) {
        for (i in 3:length(arguments)) {
          assign(argNames[i], arguments[[i]],
                 envir = tempEnvironment);
        }
      }
      print(argNames);
      print(as.list(tempEnvironment));
      print(ls(tempEnvironment));
      print(get('x', envir=tempEnvironment));
      print(get('n', envir=tempEnvironment));
      return(eval(expr = expression(expr),
                  envir = tempEnvironment)$estimate);
    }
  }
}

When running this, you get:

loopFunction(binom.test(x=x,n=n), x=c(10,20), n=c(30, 100));

#> [1] "x" "n"
#> $x
#> [1] 10
#> 
#> $n
#> [1] 30
#> 
#> [1] "n" "x"
#> [1] 10
#> [1] 30
#> Error in binom.test(x = x, n = n): object 'x' not found

So, that error stumps me. clearly, x and n exist in tempEnvironment; and tempEnvironment is passed to eval.

Why does this suddenly no longer work? Does this work differently inside functions? Am I missing something obvious?


Solution

  • I am not sure why expression() doesn't work in this context. However, it works if you write expr as a string and replace expression(expr) by parse(text=expr):

    loopFunction <- function(expr,
                             ...) {
    
      ### Get all 'dots' in a named list
      arguments <- list(...);
      argNames <- names(arguments);
    
      if (any(length(tail(arguments, -2) > 1))) {
        stop("Only the first two arguments may have length > 1!");
      }
    
      for (esIndex in seq_along(arguments[[1]])) {
        for (pwrIndex in seq_along(arguments[[2]])) {
          tempEnvironment <-
            new.env();
          assign(argNames[1], arguments[[1]][esIndex],
                 envir = tempEnvironment);
          assign(argNames[2], arguments[[2]][pwrIndex],
                 envir = tempEnvironment);
          if (length(arguments) > 2) {
            for (i in 3:length(arguments)) {
              assign(argNames[i], arguments[[i]],
                     envir = tempEnvironment);
            }
          }
          print(argNames);
          print(as.list(tempEnvironment));
          print(ls(tempEnvironment));
          print(get('x', envir=tempEnvironment));
          print(get('n', envir=tempEnvironment));
          return(eval(expr=parse(text=expr), envir =tempEnvironment)$estimate)
        }
      }
    }
    
    loopFunction("binom.test(x, n)", x=10, n=30)
    

    Result:

    > loopFunction("binom.test(x, n)", x=10, n=30)
    [1] "x" "n"
    $`x`
    [1] 10
    
    $n
    [1] 30
    
    [1] "n" "x"
    [1] 10
    [1] 30
    probability of success 
                 0.3333333