Search code examples
reval

eval: why does enclos = parent.frame() make a difference?


In this question, the following throws an error:

subset2 = function(df, condition) {
  condition_call = eval(substitute(condition),df )  
  df[condition_call,]
}

df = data.frame(a = 1:10, b = 2:11)
condition = 3

subset2(df, a < condition)
## Error in eval(substitute(condition), df) : object 'a' not found

Josh and Jason from the original question did a great job explaining why this is. What I don't get is why supplying the enclos argument to eval apparently fixes it.

subset3 = function(df, condition) {
  condition_call = eval(substitute(condition), envir = df, enclos = parent.frame())
  df[condition_call, ]
}

subset3(df, a < condition)
##   a b
## 1 1 2
## 2 2 3

I understand that skipping the function environment means R is no longer trying to evaluate the promise, and instead grabs the condition object from the global environment.

But I think supplying enclos = parent.frame() should not make a difference. From ?eval on the enclos argument:

Specifies the enclosure, i.e., where R looks for objects not found in envir.

But if not provided, it defaults to:

enclos = if(is.list(envir) || is.pairlist(envir)) parent.frame() else baseenv())

which, in my mind, should resolve to parent.frame() anyway, because surely, df satisfies the is.list() check.

This means that as long as some object data returns TRUE on is.list(), the behavior of eval(expr, envir = data) and eval(expr, envir = data, enclos = parent.frame()) should be identical. But as evidenced by the above, it isn't.

What am I missing?

EDIT: Thanks to SmokeyShakers who pointed out the difference between default and user-supplied arguments regarding the time of evaluation. I think this is actually already expressed here: https://stackoverflow.com/a/15505111/2416535

It might make sense to keep this one alive though, as it touches eval() specifically (the other does not), and it is not trivial to realize what the generalized question should be until one has the answer.


Solution

  • So, different parents. In the example that doesn't work parent.frame is looking up from inside eval into the internal environment of subset2. In the working example, parent.frame is looking up from inside subset3, likely to your global where your df sits.

    Example:

    tester <- function() {
      print(parent.frame())
    } 
    
    tester() # Global
    
    (function() {
      tester()
    })() # Anonymous function's internal env