Search code examples
rggplot2r-packagerlang

using `!!` without attaching `rlang`


I'm writing a package and one of my functions generates a ggplot. I would like to only import ggplot2 or rlang (without depending on them). After some trial and error I managed to get it to work, but now I'm not sure why it works.

So my question is, why does the following code work without directly accessing !! with ::?

arg1 <- "Species"
ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Width, y = !!rlang::sym(arg1))) + 
    ggplot2::geom_bar(stat = "summary", fun = "max")

My understanding is that in order to access the !! function I should have to specify the package with ::, but this example works so what am I missing?


Solution

  • It works because ‘rlang’/tidy evaluation doesn’t actually resolve the !! operator, it doesn’t even define such an operator — and in fact this operator doesn’t even exist! It’s just two chained ! operators which never get evaluated because tidy evaluation uses non-standard evaluation. The actual implementation in ‘rlang’ is in C++ and it’s fairly sophisticated to fix mismatches in R’s operator precedence rules, but a simplified version for a subset of the functionality could look something like this:

    bang = as.name('!')
    
    interpolate_bang_bang = function (expr, envir) {
        if (is.call(expr) && expr[[1L]] == bang) {
            if (is.call(expr[[2L]]) && expr[[2L]][[1L]] == bang) {
                eval(expr[[2L]][[2L]], envir = envir)
            } else {
                expr
            }
        } else {
            expr
        }
    }
    

    This tests if an unevaluated expression is exactly !! ‹something›, and substitutes it with an evaluated version of ‹something›. The real implementation is more complex since it needs to deal with arbitrarily complex, nested expressions (e.g. 1 + !!x) and since it does a bunch of other stuff. But the fundamental fact is illustrated above: there’s no !! operator. Instead, ‘rlang’ checks whether an unevaluated expression contains two immediately nested calls of the ! operator.

    So even if you wanted to you couldn’t import or attach a !! operator.