using `!!` without attaching `rlang`

Viewed 116

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?

2 Answers

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.

If the rlang package is loaded (or even the ggplot2 package is loaded), then using help("!!") brings up a help page naming this as the "injection operator" that suggests Konrad Rudolph's answer is wrong or at least out-of-sync with the Hadley's version of NSE parlance. The link given in a comment to Konrad's answer is more helpful: https://adv-r.hadley.nz/quasiquotation.html#the-polite-fiction-of. (Konrad being wrong would be a highly unusual instance.) The "injection operator" as it's called there, is also mentioned on `help("topic-inject-out-of-context").

!! and !!! behave specially inside all quoting functions powered by rlang, where they behave like real operators with precedence equivalent to unary + and -.

In the current instance you can also get the same result with "! ! ", which I found odd but is explained by the "polite-fiction" material.

My suspicion is that the simple act of calling ggplot2::ggplot will load a bunch of required packages into the accessible set of Namespaces even though you tried to prevent that by using the "::" method. So the "!!" does get evaluated in the NSE manner despite efforts to prevent that occurring:

#In an entirely  base session with only required packages loaded ....
ggplot2::ggplot(iris, ggplot2::aes(x = Petal.Width))
#-----------------------------------
> sessionInfo()
R version 4.1.2 (2021-11-01)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/libopenblasp-r0.2.20.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

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

loaded via a namespace (and not attached):
 [1] magrittr_2.0.2   tidyselect_1.1.2 munsell_0.5.0    colorspace_2.0-3 R6_2.5.1         rlang_1.0.2     
 [7] fansi_1.0.2      dplyr_1.0.8      tools_4.1.2      grid_4.1.2       gtable_0.3.0     utf8_1.2.2      
[13] cli_3.2.0        DBI_1.1.2        ellipsis_0.3.2   assertthat_0.2.1 digest_0.6.29    tibble_3.1.6    
[19] lifecycle_1.0.1  crayon_1.5.0     purrr_0.3.4      ggplot2_3.3.5    vctrs_0.3.8      glue_1.6.2      
[25] labeling_0.4.2   compiler_4.1.2   pillar_1.7.0     generics_0.1.2   scales_1.1.1     pkgconfig_2.0.3 
Related