R eval has misleading documentation for the case that the envir argument is list or pairlist
eval, metaprogramming, r
Solution
For this specific example, in the first case, the definition of `f3` is:
f3
#function(){
# eval(quote(y), envir = list())
# }
#<environment: 0x02a41584>
The default `enclos = parent.frame()` argument will be evaluated in the evaluation frame (i.e. "inside") of `eval` -- the parent of `eval`s current environment is the current `environment()` of its wrapper function (`f3`). And its wrapper function "remembers" where it was created and searches for the `y` correctly to find it.
In the second case, `f3` is defined as:
f3
#function(){
# eval(quote(y), envir = list(),
# enclos = parent.frame())
# }
#<environment: 0x02a4e5e4>
Here, `enclos = parent.frame()` is evaluated in the evaluation `environment()` of `f3` and, upon calling `f3()`, its parent is the `.GlobalEnv` where there is no `y`.
As a clearer example (yet equally nested as the example) we could consider:
f0 = function(e) print(e)
f1 = function(e = parent.frame()) f0(e)
fA = function() #returns a function
{
function()
{
print(parent.frame())
print(environment())
f1()
}
}
fB = function() # returns a function
{
function()
{
print(parent.frame())
print(environment())
f1(parent.frame())
}
}
And call:
fA()()
#<environment: R_GlobalEnv> #<- parent of `fA()`'s `environment()`
#<environment: 0x06ff25bc> #<- current env of `fA()`
#<environment: 0x06ff25bc> #<- parent of `f1` == current of `fA()`
fB()()
#<environment: R_GlobalEnv> #<- parent of `fA()`'s `environment()`
#<environment: 0x06fec304> #<- current env of `fA()`
#<environment: R_GlobalEnv> #<- parent is `eval`ed as parent of `fA()`'s current
As Taz notes, the distinction between default and supplied arguments is stated in the definition manual. Fooling a bit around we could see it in action by trying to reach the promises of arguments:
First, a helper function to access the promises of current arguments:
.ff = inline::cfunction(sig = c(symarg = "symbol", env = "environment", penv = "environment"), body = '
SEXP arg = findVar(symarg, env), ans = allocVector(VECSXP, 5);
SET_VECTOR_ELT(ans, 0, PRCODE(arg));
SET_VECTOR_ELT(ans, 1, PRENV(arg));
SET_VECTOR_ELT(ans, 2, eval(PRCODE(arg), PRENV(arg)));
SET_VECTOR_ELT(ans, 3, env);
SET_VECTOR_ELT(ans, 4, penv);
return(ans);
')
And the function whose arguments we 'll track:
ff = function(arg = parent.frame())
{
ans = setNames(.ff(quote(arg), environment(), parent.frame()),
c("expr", "envir", "val", "cur", "par"))
cat(sprintf("promise:\n\tcall: '%s'\n\tsearched at: '%s'\n\tfound as: '%s'\ncurrent: '%s'\nparent: '%s'\n%s\n",
deparse(ans$expr), capture.output(ans$envir),
capture.output(ans$val), capture.output(ans$cur),
capture.output(ans$par), strrep("-", 40)))
return(invisible(ans))
}
And a simple example:
ff()
#promise:
# call: 'parent.frame()'
# searched at: '<environment: 0x06fff594>'
# found as: '<environment: R_GlobalEnv>'
#current: '<environment: 0x06fff594>'
#parent: '<environment: R_GlobalEnv>'
#----------------------------------------
ff(parent.frame())
#promise:
# call: 'parent.frame()'
# searched at: '<environment: R_GlobalEnv>'
# found as: '<environment: R_GlobalEnv>'
#current: '<environment: 0x06fcce20>'
#parent: '<environment: R_GlobalEnv>'
#----------------------------------------
Or a more nested case:
fnest1 = function() ff()
fnest2 = function() ff(parent.frame())
fnest1()
#promise:
# call: 'parent.frame()'
# searched at: '<environment: 0x028d1ccc>'
# found as: '<environment: 0x028d1d20>'
#current: '<environment: 0x028d1ccc>'
#parent: '<environment: 0x028d1d20>'
#----------------------------------------
fnest2()
#promise:
# call: 'parent.frame()'
# searched at: '<environment: 0x026866b8>'
# found as: '<environment: R_GlobalEnv>'
#current: '<environment: 0x0268662c>'
#parent: '<environment: 0x026866b8>'
#----------------------------------------
Problem
I am not sure if this should be posted on R-devel (if so, just let me know...) but there seems to be at least a mistake in the documentation of R's `eval()` function, which is essential for R's Non Standard evaluation features, or what am I getting wrong? ``` > eval function (expr, envir = parent.frame(), enclos = if (is.list(envir) || is.pairlist(envir)) parent.frame() else baseenv()) .Internal(eval(expr, envir, enclos)) <bytecode: 0x0000000009534280> <environment: namespace:base> ``` This and also this part of `eval`'s help on the `enclos` argument ``` > Relevant when envir is a (pair)list or a data frame. Specifies the enclosure, i.e., where R looks for objects not found in envir. This can be NULL (interpreted as the base package environment, baseenv()) or an environment. ``` indicates that whenever a list is supplied to `eval()`'s `envir` argument, the value of `enclos` is (lazily) evaluated to `parent.frame()`, so it should be the same as supplying `parent.frame()` additionally (but this time directly). However, note that the following two code chunks where I only change this, don't return the same. ``` > rm(list = ls(all = TRUE)) > f1 <- function(){ y <- 2 f2 <- function(){ eval(quote(y), envir = list()) } f2 } > f3 <- f1() > f3() [1] 2 ``` and ``` > rm(list = ls(all = TRUE)) > f1 <- function(){ y <- 2 f2 <- function(){ eval(quote(y), envir = list(), enclos = parent.frame()) } f2 } > f3 <- f1() > f3() Error in eval(expr, envir, enclos) : object 'y' not found ``` So the only conclusion I can get from here is that it seems, that for data frames and lists the enclosing environment per default is not the `parent.env()`. Instead it seems that R is looking in the current execution environment. Here the only relevant part that I can find in `?eval` on that: ``` > When evaluating expressions in a data frame that has been passed as an argument to a function, the relevant enclosure is often the caller's environment, i.e., one needs eval(x, data, parent.frame()). ``` Which somehow indicates, that one should set `parent.frame()` explicitly. And that the indicated behaviour above is indeed not what `eval()` does by default...(at least for the case, when `envir` is set to a list/pairlist). For me this is very confusing. However, I am not sure if I get something wrong and would appreciate any answer or comment on that.