Ralph
Ralph

Reputation: 216

What is the difference between expr and enexpr using tidy evalution in R?

In learning metaprogramming with R, I'm trying to understand the different concepts and mechanisms that form the tidy evaluation framework. I don't really understand the difference between expr() and enexpr() (or quo() and enquo() and all their plurals for that matter).

The only thing I understand is that the enriched en-versions should be used for capturing function arguments. However, experimenting with this is giving me mixed results:

library(rlang) 

x <- 1000 
a <- 9 
b <- 2 
    
f1 <- function(x) { 
  e <- expr(x) 
  print(e) 
  print(eval(e)) 
} 

f2 <- function(x) { 
  e <- enexpr(x) 
  print(e) 
  print(eval(e)) 
} 

Feeding these functions an expression, the output is this:

> f1(a + b) 
x 
[1] 11

> f2(a + b) 
a + b 
[1] 11 

From that, several things don't make sense to me. In the first function with expr(), even though the expression is just x in its quoted/defused form when printed, it evaluates to 11, the value of a + b. So even if it does not find x within the function scope, shouldn't it be able to find x <- 1000 in the global environment?

So then I thought, the only explanation is that there would still be a presence of a and b in the abstract syntax tree (AST) of the expression e. Indeed it seems to go out of its way to still find a and b in the global environment, choosing them over x. But why is that?

Upvotes: 1

Views: 64

Answers (1)

MrFlick
MrFlick

Reputation: 206232

The differences between expr and enexpr has to do with lazy evaluation. R doesn't evaluate the value of parameters passed to function until the value is actually used. Until they are evaluated, they are in a "promise" state. The expr() function basically takes whatever expression you pass to it and keeps it an expression. enexpr() however takes a symbol, and grabs the expression contained in the promise that variable points to (assuming it hasn't been evaluated). Something like expr(a+b) is valid, but enexpr(a+b) is invalid. You can only pass a symbol to enexpr

Your functions won't find x in the global environment because you've created a shadow variable also named x in if your function body. It will contain the value passed to the function. So when you evaluate the symbol x, you get the value that x has in your local function, not the global environment. Just like if you call

f <- function(x) {
  x
}
f(a+b)
[1] 11

you get the value you would expect. If you change the variable name, you get the different behavior

f3 <- function(z) {   # note "z" here, no longer masking global "x"
  e <- expr(x) 
  print(e) 
  print(eval(e)) 
} 
f3(a+b)
# x
# [1] 1000

Upvotes: 0

Related Questions