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 1003

The 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 13

Whenever you use a data mask, you must always use enquo() instead of enexpr(). This is the topic of Chapter 20.