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