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?
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.