I created a function that returns expression as text except when the input is NULL and wondered why the following two functions using tidyeval and base-NSE behave differently:

f_rlang <- function(x) {
if (is.null(x)) return("")
rlang::expr_text(rlang::enexpr(x))
}
f_base <- function(x) {
if (is.null(x)) return("")
deparse(substitute(x))
}
symbol <- "content"
f_rlang(symbol)
#> [1] "\"content\""
f_base(symbol)
#> [1] "symbol"

^{Created on 2018-10-02 by the reprex package (v0.2.1)}

After some investigation, in tidyeval I found I have to capture x before evaluating it. So, the correct version would be:

f_rlang2 <- function(x) {
x_expr <- rlang::enexpr(x)
if (is.null(x)) return("")
rlang::expr_text(x_expr)
}
symbol <- "content"
f_rlang2(symbol)
#> [1] "symbol"

^{Created on 2018-10-02 by the reprex package (v0.2.1)}

But, is this what is supposed to be? If this is quo(), it may be reasonable that it matters whether the promise (right?) is evaluated or not. But, this is expression, which is separated from the environments and doesn't know when and where to get evaluated.

Since I'm not familiar with tidyeval's concepts, I'm not really sure if I should file a issue for this on rlang's repo. Does anyone know are there any nice explanation for this difference between tidyeval and base-NSE?

In terms of base functions, enexpr(arg) corresponds to base::substitute(arg) (though that function has complex semantics) and expr() is like quote() (and bquote() if we consider unquotation syntax). The plural variant exprs() is equivalent to base::alist() . Finally there is no function in base R that is equivalent to enexprs() but you can reproduce its behaviour with eval(substitute(alist(...))) .

So I think you can just use:

library(rlang, warn.conflicts = FALSE)
f_rlang3 <- function(x) {
if (is.null(x)) return("")
rlang::enexpr(x)
}
symbol <- "content"
f_rlang3(symbol)
#> [1] "content"

^{Created on 2018-10-02 by the reprex package (v0.2.1.9000)}

A forced promise can no longer be captured correctly because it no longer carries an environment. enexpr() does not need the environment but we chose to make all capture functions consistent with enquo().

Right but even without this, expr() can return constants: expr(!!letters). In fact tidy eval is implemented in such a way that capturing a forced argument or an unquoted value is strictly equivalent. It is thus good practice to always keep in mind unquoted values when designing your quoting function. This shouldn't be a problem if you're taking an evaluation approach (rather than a parsing or labelling approach).

OK, now I feel it's fair, thanks for the explanation. What I wanted to do was labelling, so I'll wait for enlabel() and stick with substitute() for labelling purpose until the time comes