Search code examples
rrlangnse

Understanding assignment when using rlang::eval_tidy


I want to understand why the following happens when evaluating <- assignment operations using rlang::eval_tidy.

Let's use rlang::expr to create an expression to assign the variable x a value 1.

x_expr <- rlang::expr(x <- 1)

We also create an environment in which we want to evaluate the expression.

new_env <- new.env(parent = parent.env(environment()))

My intention is that we assign 1 to x inside new_env.

Now, we use base::eval to evaluate this expression:

eval(x_expr, envir = new_env)
get('x', envir = new_env)

This returns 1. If we simply evaluate x in the global environment, we get an error (indicating x is in new_env and not taken from the global environment).
We can remove x from new_env, and then re-assign it as we'd like using rlang::eval_bare:

rm('x', envir = new_env)
rlang::eval_bare(x_expr, env = new_env)
get('x', envir = new_env)

Again, this gives 1.

Now, we remove x from new_env again, and try to re-assign it using rlang::eval_tidy. However, in this case we get an error when we look for x in new_env:

rm('x', envir = new_env)
rlang::eval_tidy(x_expr, env = new_env)
get('x', envir = new_env)

Why does this happen? I presume x has been assigned 1, but where?

sessionInfo():

R version 4.1.0 (2021-05-18)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19042)

Matrix products: default

locale:
[1] LC_COLLATE=English_South Africa.1252  LC_CTYPE=English_South Africa.1252   
[3] LC_MONETARY=English_South Africa.1252 LC_NUMERIC=C                         
[5] LC_TIME=English_South Africa.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] compiler_4.1.0 tools_4.1.0    yaml_2.2.1     rlang_0.4.11  

Solution

  • From the documentation on eval_tidy():

    eval_tidy() always evaluates in a data mask, even when data is NULL. Because of this, it has different stack semantics than base::eval():

    • Lexical side effects, such as assignment with <-, occur in the mask rather than env.

    We can confirm this by adding a data mask during tidy_eval()

    library(rlang)
    
    x_expr <- rlang::expr(x <- 1)
    new_env <- new.env(parent = parent.env(environment()))
    data_mask <- rlang::new_data_mask(new.env(parent = parent.env(environment())))
    
    rlang::eval_tidy(x_expr, data_mask, env = new_env)
    
    get('x', envir = data_mask) ## Returns 1