r-lib / rlang

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

`enexpr()` returns a quosure when provided an interpolated {{ arg }} #1523

Closed moodymudskipper closed 1 year ago

moodymudskipper commented 1 year ago
library(rlang)
foo <- function(x) {
  print(enexpr(x))
  bar({{ x }})
}
bar <-function(x) {
  enexpr(x)
}

foo(y)
#> y
#> <quosure>
#> expr: ^y
#> env:  global

As a consequence this will have an unexpected output :

f1 <- function(x) f2({{ x }})
f2 <- function(x) inject(aggregate(!!enexpr(x) ~ Species, iris, mean))

f2(Petal.Width)
#>      Species Petal.Width
#> 1     setosa       0.246
#> 2 versicolor       1.326
#> 3  virginica       2.026
f1(Petal.Width)
#> Error in `aggregate()`:
#> ! Base operators are not defined for quosures. Do you need to unquote
#>   the quosure?
#> 
#> # Bad: myquosure == rhs
#> 
#> # Good: !!myquosure == rhs

# workaround
f2 <- function(x) inject(aggregate(!!quo_squash(enexpr(x)) ~ Species, iris, mean))
f1(Petal.Width)
#>      Species Petal.Width
#> 1     setosa       0.246
#> 2 versicolor       1.326
#> 3  virginica       2.026
lionel- commented 1 year ago

This is the expected behaviour.

moodymudskipper commented 1 year ago

It's undocumented then AFAICT, I see:

enexpr() and enexprs() are like enquo() and enquos() but return naked expressions instead of quosures. These operators should very rarely be used because they lose track of the environment of defused arguments.

My intuition is that enexpr(x) should be equivalent to quo_expr(enquo(x))

lionel- commented 1 year ago

It could make sense to make it equivalent to quo_squash(enquo(x)). But then every round of enexpr() would fully traverse the AST. Even if we treat {{ differently, !! could still be used to inject a quosure, hence we'd have to use quo_squash().

With the current design, the caller of enexpr() (which is for experts) is in charge of squashing quosures if needed.