17.8 Quosures
To make the problem more obvious, I’m going to modify with2(). The basic problem still occurs without this modification but it’s much harder to see.
with2 <- function(df, expr) {
a <- 1000
eval_tidy(enexpr(expr), df)
}We can see the problem when we use with2() to refer to a variable called a. We want the value of a to come from the binding we can see (10), not the binding internal to the function (1000):
df <- data.frame(x = 1:3)
a <- 10
with2(df, x + a)
#> [1] 1001 1002 1003The problem arises because we need to evaluate the captured expression in the environment where it was written (where a is 10), not the environment inside of with2() (where a is 1000).
Fortunately we can solve this problem by using a new data structure: the quosure which bundles an expression with an environment. eval_tidy() knows how to work with quosures so all we need to do is switch out enexpr() for enquo():
with2 <- function(df, expr) {
a <- 1000
eval_tidy(enquo(expr), df)
}
with2(df, x + a)
#> [1] 11 12 13Whenever you use a data mask, you must always use enquo() instead of enexpr(). This is the topic of Chapter 20.