Recursive Evaluation in the Global Environment
In this article, we will delve into the world of recursive evaluation and explore why rapply does not work as expected when trying to evaluate expressions in the global environment. We will also discuss the nuances of lapply, how to work around the issues with rapply, and provide examples to illustrate our points.
Understanding Rapply
rapply is a function in R that applies a function to each element of a list or vector. It differs from lapply in its behavior, particularly when it comes to evaluating arguments before calling the C code.
The documentation for rapply states that:
“The semantics differ in detail from lapply: in particular the arguments are evaluated before calling the C code.”
This means that when using rapply, the arguments are evaluated immediately, rather than being passed as a list of values to be evaluated later. This can lead to unexpected behavior and errors.
Example: rapply with stop(“error”)
To illustrate this point, let’s consider an example using rapply. We’ll try to apply a function that stops R with the message “error”.
rapply(list(quote(stop("error"))), function(x) x)
# Error in (function (x) : error
lapply(list(quote(stop("error"))), function(x) x)
# [[1]]
# stop("error")
As we can see, rapply fails with an error message, while lapply succeeds and returns the expected result.
Recursive Evaluation in rapply
Now that we’ve explored how rapply works, let’s dive into our original question. We have a nested list of expressions and want to evaluate them recursively using rapply. However, we’re having trouble getting x to change in the global environment.
# 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.
We’re using rapply with eval as our function, and passing the global environment as the environment for evaluation. However, we’re not getting the expected results.
Workaround: Using evalq
As suggested in the answer to our question, we can use evalq instead of eval. This allows us to evaluate expressions using a function that takes an expression as input, rather than applying a fixed function to each element of a list.
rapply(exprs, evalq, envir = globalenv()) # updated with Hadley's equivalent but cleaner version.
# [1] 10 5 8 24
x
# [1] 24
As we can see, rapply now works as expected, and x is changed in the global environment.
Substitute: Salvaging the Original Expression
However, there’s still a problem. The actual expression being evaluated is not our original expression, but rather the result of evaluating 10 in the global environment. This is because evalq evaluates the expression using the current value of x, which we’ve set to 24.
To salvage the original expression, we can use the substitute function, which allows us to replace variables with their values.
substitute(expr1)
# expr1
By using substitute, we can replace the variable x in our original expressions with its current value. This ensures that our final results are accurate and consistent.
Flatten: A Utility Function for List Manipulation
In order to use lapply or rapply effectively, we need to flatten the list of expressions into a single list. We can do this using a utility function called flatten.
flatten <- function(x) {
repeat {
if(!any(vapply(x, is.list, logical(1)))) return(x)
x <- Reduce(c, x)
}
}
# Generate a nested list of expressions
exprs <- list(expr1, list(expr2, expr3), expr4)
# Flatten the list of expressions
flattened_exprs <- flatten(exprs)
By using flatten, we can easily transform our nested list of expressions into a single list that we can evaluate using lapply or rapply.
Conclusion
In this article, we’ve explored why rapply does not work as expected when trying to evaluate expressions in the global environment. We’ve also discussed how to use evalq and substitute to salvage our original expressions and achieve the desired results.
Additionally, we’ve introduced a utility function called flatten, which allows us to transform nested lists of expressions into single lists that can be evaluated using lapply or rapply.
By understanding these nuances and using the right tools for the job, you’ll be able to work effectively with recursive evaluation in R.
Last modified on 2024-02-14