Search code examples
rrlangtidyeval

functions accessing wrong variables in eval_tidy


So I can't for the life of me understand what is going on here. This was supposed to be a small exploration of a possible edge case, but it wound up revealing how little I understand R/tidy evaluation.

I want to understand why, when a function is coded to access a variable it doesn't define in its arguments, eval_tidy can't seem to mask the variables it uses.

Consider the following, where f accesses a variable named sup that isn't passed in as an argument

library(rlang) # I'm using rlang 0.3.0

# Define `sup` globally
sup <- "yes"

f <- function() {
  # Access whatever the `sup` variable is
  if (sup == "yes") "YES"
  else "NO"
}

f()
# 'YES'

q <- quote(f())
eval_tidy(q, c(sup="no"))
# 'YES'

I've tried just about everything, but I can't seem to mask sup from f().


Solution

  • Data masking operates on expressions, quote(f()) in your example. However the masking is lexical, not dynamic. This means that the functions called from the expression won't be able to see the masked bindings.

    There's a way to achieve what you want. You can add the masking bindings in an environment inheriting from the function's original environment, and replace the latter with the former. rlang makes this rather easy:

    library("rlang")
    
    sup <- "yes"
    f <- function() if (sup == "yes") "YES" else "NO"
    
    mask <- env(get_env(f), sup = "no")
    f <- set_env(f, mask)
    
    f()
    #> [1] "NO"
    

    However, that seems like a rather odd design choice that might give a weird feeling to your UI/DSL.