In my two previous post on non-standard evaluation, Scoping rules and NSE and Overscoping and eval, I explained:

I finished the last post with a model-fitting example, where the lm function fits data to a formulae, by saying that this particular example had more going on than you might expect. When we write something like

lm(y ~ x, data = df)

it looks as if the lm function uses over-scoping to search for x and y in the df data frame first, and if that fails, it looks ine the calling scope. This is something we might implement using eval and parent.frame (or the more aptly named rlang::caller_env). This is, in fact, not what is happening. It almost is, yes, but not quite. The lm function does use overscoping to put df in front of environments, but it does not look in the calling environment; it looks in the formulas environment.1

x <- rnorm(5); y <- rnorm(5)
lm(y ~ x)
## Call:
## lm(formula = y ~ x)
## Coefficients:
## (Intercept)            x  
##     -1.1625      -0.5801

We will use this function to contrast an environment from a closure with one we create at the global scope:

closure_formula <- function() {
    x <- rnorm(5); y <- rnorm(5)
    y ~ x

The graph of environments looks like this: