Home > Enterprise >  How can evalq source a file in correct environment if within a function
How can evalq source a file in correct environment if within a function

Time:10-15

I have a function sourcing a script in its parent environment. The following works as expected:

cat("aa <- 77", file="script.R")
runScript <- function() source("script.R", local=parent.frame())

env <- new.env()
evalq(runScript(), envir=env)
ls.str(envir=env) # as expected: aa in env, not in globalEnv
rm(env)

When I wrap this in a function, the environment seems to be ignored:

thisfails <- function(expr){
  env <- new.env()
  evalq(expr, envir=env)
  ls(envir=env)
}

thisfails(runScript()) # why is aa in globalEnv?
rm(aa)

Now I would understand this with eval, but not with evalq, as the manual says: eval evaluates its first argument in the current scope before passing it to the evaluator: evalq avoids this.

Interstingly enough, this works:

works <- function(){
  runScript()
  ls()
}
works() # as expected: returns aa, doesn't create it in globalEnv

How can I use runScript as an expression inside a function and avoid it to be evaluated in globalEnv?

CodePudding user response:

This fails because your function thisfails itself is using standard evaluation: expr is evaluated before being passed to evalq.

Instead, you need to capture the unevaluated argument via substitute, and pass the resulting expression to eval:

this_works <- function (expr) {
  env <- new.env()
  eval(substitute(expr), envir = env)
  ls(envir = env)
}

The confusing thing is that this looks exactly like the implementation of evalq! — after all, evalq(expr) is implemented as:

.Internal(eval(substitute(expr), envir, enclos))

However, it is more accurate to think of evalq(expr) as being equivalent to eval(quote(expr)). That is, expr is understood verbatim, and is never evaluated. The actual implementation of evalq looks different simply because it needs to first retrieve the unevaluated expression represented by its expr argument in the calling scope, and that’s exactly what substitute does when called on an argument.

… Confusingly, substitute does something different for names in the global environment; namely, it leaves them untouched:

x = 1
substitute(x   2)
# x   2

in_function = function () {
    x = 1
    substitute(x   2)
}

in_function()
# 1   2

in_env = new.env()
in_env$x = 1
substitute(x   2, in_env)
# 1   2
  •  Tags:  
  • r
  • Related