R eval has misleading documentation for the case where the envir argument is a list or a pairwise list
I'm not sure if this should be posted on R-devel (if yes, just let me know ...), but there seems to be at least a bug in the documentation of the R function eval()
that is needed for the R Non Standard evaluation functions or what am I 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 as well as this part eval
will help in the argumentenclos
> 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 as an argument eval()
envir
, the value enclos
is evaluated (lazily) on parent.frame()
, so it should be the same as supplying parent.frame()
extra (but this time itself). Note, however, that the next two code snippets where I only change this, do not 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 apparently for data frames and lists, the default environment is not parent.env()
. Instead, R appears to be looking at the current runtime. Here's the only relevant part I can find in ?eval
about this:
> When evaluating expressions in a data frame that has been passed as an
argument to a function, the relevant enclosure is often the caller
environment, i.e., one needs eval(x, data, parent.frame()).
Something indicates that you need to explicitly specify parent.frame()
. And that the above behavior really doesn't match eval()
by default ... (at least for the case when envir
set to a list / pair list).
This is very confusing for me. However, I'm not sure if there is anything wrong, and would appreciate any response or comment.
source to share
For this particular example, in the first case, the definition is f3
:
f3
#function(){
# eval(quote(y), envir = list())
# }
#<environment: 0x02a41584>
By default, the argument enclos = parent.frame()
will be evaluated in the evaluation frame (ie "inside") eval
- the parent of the current environment eval
is environment()
its current wrapper function ( f3
). And its wrapper function "remembers" where it was created and looks right y
to find it.
In the second case, it f3
is defined as:
f3
#function(){
# eval(quote(y), envir = list(),
# enclos = parent.frame())
# }
#<environment: 0x02a4e5e4>
Here, enclos = parent.frame()
evaluates to environment()
of f3
and, by calling f3()
, its parent is .GlobalEnv
where not y
.
As a clearer example (still equally nested as an example), we might 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 the call:
fA()()
#<environment: R_GlobalEnv> #<- parent of `fA()` `environment()`
#<environment: 0x06ff25bc> #<- current env of `fA()`
#<environment: 0x06ff25bc> #<- parent of `f1` == current of `fA()`
fB()()
#<environment: R_GlobalEnv> #<- parent of `fA()` `environment()`
#<environment: 0x06fec304> #<- current env of `fA()`
#<environment: R_GlobalEnv> #<- parent is `eval`ed as parent of `fA()` current
As Taz points out, the difference between default arguments and supplied arguments is stated in the manual. Fooling around a bit, we could see this in action trying to find the promises arguments:
First, a helper function to access the promises of the 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 will 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>'
#----------------------------------------
source to share