r-lib / rlang

Low-level API for programming with R
https://rlang.r-lib.org
Other
509 stars 138 forks source link

Clean intermediate environments of the overscope #160

Closed egnha closed 6 years ago

egnha commented 7 years ago

As intended, quos() captures the calling environment:

quos(x)

#> [[1]]
#> <quosure: global>
#>   ~x
#> 
#> attr(,"class")
#> [1] "quosures"

But if you tidyeval a quosure of quos(), the calling environment is not passed on to quos(); instead, some internally generated environment is captured (an overscope?):

eval_tidy(quo(quos(x)))

#> [[1]]
#> <quosure: local>
#>   ~x
#> 
#> attr(,"class")
#> [1] "quosures"

I would have expected eval_tidy(quo(quos(x))) to capture the global environment, as quos(x) did (and as eval_tidy(quo(!! quos(x))) would).

Have I misunderstood something basic about the design/proper use of tidyeval?

lionel- commented 7 years ago

quo() quotes, so you get quos(x) quoted, not evaluated. It's then evaluated by eval_tidy() in an overscope. You found the proper way to do it: you need to unquote the parts that should be evaluated right away:

quo(!! quos(x))

By the way, you can post these questions on stackoverflow with the tidyeval tag and I'll get notifications.

egnha commented 7 years ago

Thanks for the clarification.

I notice now that the environment captured by eval_tidy(quo(quos(x))) is a chain of two environments with empty frames, chained to the calling environment (global env, in this case):

q <- quo(quos(x))
e <- get_env(eval_tidy(q)[[1]])
chain <- Reduce(function(., ..) parent.env(.), 1:2, e, accumulate = TRUE)

chain

#> [[1]]
#> <environment: 0x7fd2f9bedc98>
#>   
#> [[2]]
#> <environment: 0x7fd2f9bc97c8>
#>   
#> [[3]]
#> <environment: R_GlobalEnv>

unlist(lapply(chain[1:2], ls, all.names = TRUE))

#> character(0)

I suppose this is by design (and I am guessing the intermediate environments are the bottom/top of an overscope), and that these environments are (mostly) harmless, vis-à-vis evaluation. But I still find it a bit puzzling that they are there, and I don't understand their purpose (i.e., why they are not "cleaned-up") ...

lionel- commented 7 years ago

We have to return the bottom of the overscope because users might have created new values there, which might be later referred to if the quosure evaluates to a formula or a function (as these objects capture the dynamic environment in which they were evaluated).

The environments you see are empty because they were cleaned up after evaluation. But I guess we could rechain the bottom of the overscope to the environment of the outermost quosure to clean things further.

egnha commented 7 years ago

Thanks for the further clarification. That would be a welcome change, for I think it would help one to reason about quosures more consistently.

lionel- commented 7 years ago

you can use env_parents() by the way.

lionel- commented 6 years ago

We no longer clean the overscope/data mask.