Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive eval in the global environment

Tags:

r

I am having a hard time understanding why my attempt to recursively apply eval, using rapply, is not working.

I have a nested list of expressions:

# Some expressions   
expr1 <- quote(x <- x + 9)
expr2 <- quote(x <- x/2)
expr3 <- quote(x <- x + 3)
expr4 <- quote(x <- x * 3)

# Generate a nested list of expressions
exprs <- list(expr1, list(expr2, expr3), expr4)

# Initialize x, and attempt to eval the expressions
x <- 1
rapply(exprs, eval, envir = globalenv())
# Returns: [1] 10  5  8 24, but x is not changed.

Clearly the recursion worked, but it did not evaluate the expression in the global environment, like I specified. Based on this answer I flattened out the list, and can eval using lapply.

flatten <- function(x) {
  repeat {
    if(!any(vapply(x, is.list, logical(1)))) return(x)
    x <- Reduce(c, x)
  }
}

# Both change x to 24 in the global env, like desired.
lapply(flatten(exprs), eval, envir = globalenv())
lapply(flatten(exprs), eval, envir = parent.frame())

As far as I understand rapply/lapply evaluate in their own environment, just like any other function, then return a value. It makes sense that I can specify the global environment, or the parent frame (because the parent frame of lapply should be the environment it was called in - here the global environment.)

Following this logic I would expect specifying the global environment with rapply to work. Specifying the parent frame should fail (and it does), because I am assuming the calls nested one deep within rapply get evaluated in the environment created by the original call to rapply.

What am I missing here? Why doesn't the rapply call change x in the global environment?

like image 387
dayne Avatar asked Mar 21 '14 12:03

dayne


1 Answers

Note from documentation for rapply:

The semantics differ in detail from lapply: in particular the arguments are evaluated before calling the C code.

Try the following to see what they mean:

rapply(list(quote(stop("error"))), function(x) x)
# Error in (function (x)  : error
lapply(list(quote(stop("error"))), function(x) x)
# [[1]]
# stop("error")

You can try this as a workaround:

rapply(exprs, evalq, envir = globalenv())  # updated with Hadley's equivalent but cleaner version.
# [1] 10  5  8 24
x
# [1] 24

As you noted, x is getting evaluated in the rapply environment, which is why the result makes sense, but the actual eval statement is not of your original expression, but of the result (i.e. in the first iteration, 10 gets evaluated in the global env, not x <- x + 9). By using substitute we're able to salvage the original expression.

like image 89
BrodieG Avatar answered Oct 07 '22 08:10

BrodieG