2

This question is related to the one posted here.

In short I am looking for why base::substitute and rlang::enexpr are behaving differently below.

#works
f1 <- function(x,y){ 
  do.call("methods", list(substitute(x::y)))
}
f1(broom,tidy)
#does not work
#Error: `arg` must be a symbol
f2 <- function(x,y){
  do.call('methods',list(rlang::enexpr(x::y)))
}
f2(broom,tidy)

The longer version. In the advanced R book chapter 19 you can see tables 19.1 and 19.2 suggest that enexpr and substitute should serve the same purpose in a function call (I could be wrong here and would be happy with an explanation on why I am wrong).

I decided to test this out and saw that in f1 returns results but f2 returns an error.

Interestingly, if you use do.call('methods',list(rlang::expr(broom::tidy))) this works. I find this interesting because rlang::expr simply calls rlang::enexpr.

In the question above, MrFlick posted this function as a pure rlang solution

#also works 
#function from MrFlick in the posted link
f3 <- function(x,y){
x <- rlang::ensym(x)
y <- rlang::ensym(y)
rlang::eval_tidy(rlang::quo(methods(`::`(!!x, !!y))))}

f3(broom,tidy) 

This seems to be a bit more complicated than I was expecting.

It would be helpful to know why f1 and f2 are not equivalent or how to make f2 work with enexpr.

Mike
  • 3,797
  • 1
  • 11
  • 30

1 Answers1

2

rlang::enexpr() and base::substitute() are not quite equivalent in their interface. enexpr() expects a single variable name that refers to one of the function's input argument, whereas substitute() can work with arbitrary expressions. This requires additional expression arithmetic -- by wrapping with rlang::expr() and using unquote operator !! -- to place the result of enexpr() into a more complex expression:

g  <- function(x) substitute(x+5)
h  <- function(x) rlang::enexpr(x+5)
h2 <- function(x) rlang::expr( !!rlang::enexpr(x) + 5 )

g(a)   # a + 5
h(a)   # Error: `arg` must be a symbol
h2(a)  # a + 5

To make f2 work, you need to apply rlang::enexpr() to each argument separately, then use expression arithmetic to compose the overall x::y expression:

f2 <- function(x,y){
   ee <- rlang::expr( `::`(!!rlang::enexpr(x), !!rlang::enexpr(y)) )
   do.call('methods',list(ee))
}
f2(broom,tidy)

Note that we have to use :: in prefix notation, because having !! and :: next to each other is a problem for the parser. In other words, expressions of the type !!a :: !!b result in parsing errors.

The alternative is to let the user compose the expression themselves. This way you can get away with a single rlang::enexpr():

f3 <- function(x)
  do.call('methods',list(rlang::enexpr(x)))
f3( broom::tidy )

Side Note:

Even though expr() calls enexpr(), that call is with respect to the scope of expr() itself. Consider,

f1 <- function(x)  rlang::enexpr(x)

f2 <- function(x) {
  g <- function(y) rlang::enexpr(y)
  g(x)
}

The two are not equivalent, because the second enexpr() is scoped to the inner function g(), not to the outer f2().

f1(abc)   # abc
f2(abc)   # x
Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74