Non-standard evaluation (NSE) often gets used together with tidyverse/dplyr, but most people encounter it on a daily basis when they load packages.
a <- "rlang"
print(a) # Standard evaluation: the expression a is replace by its value
# [1] "rlang"
library(a) # Non-standard evaluation: the expression a is used as-is
# Error in library(a) : there is no package called ‘a’
So, how do you load a dynamically specified package? Here, we will use quasiquotation for demonstration. (In real code, I recommend doing library(a, character.only=TRUE)
instead.)
In base R, you can use bquote()
to dynamically construct an expression and then evaluate it.
myexpr <- bquote(library(.(a))) # myexpr will now be library("rlang")
eval(myexpr) # rlang is now loaded
rlang
provides additional tools to manipulate expressions. In general, they allow you to be more expressive than the base R tools. The !!
behaves similarly to the above:
myexpr <- rlang::expr(library(!!a)) # Same as above, myexpr is now library("rlang")
You can use rlang::expr
with !!
to construct any expressions for future evaluation.
x <- rlang::expr(mtcars)
y <- rlang::expr(mpg > 30)
z <- rlang::expr(disp)
rlang::expr(subset(!!x, !!y, !!z)) # Constructs subset(mtcars, mpg > 30, disp)
When you have a lot of arguments, you can put them in a list and use the !!!
shortcut. The above expression can be replicated with
l <- rlang::exprs(mtcars, mpg > 30, disp) # Note the s on exprs
rlang::expr(subset(!!!l)) # Also builds subset(mtcars, mpg > 30, disp)
The {{
operator is the most complicated one to explain and requires an introduction of quosures.
Expressions in R are first-class objects, which means that they can be passed into functions, returned by functions, etc. However, expressions created with rlang::expr
are always evaluated in their immediate context. Consider,
a <- 10
x <- rlang::expr(a+5)
f <- function(y) {
a <- 5
eval(y)
}
f(x) # What does this return?
Even though the expression x
captures a+5
, the value of a
changes right before the expression is evaluated. Quosures capture expressions AND the environments where they are defined. That environment is always used to evaluate that expression.
a <- 10
x <- rlang::quo(a+5) # Quosure = expression + environment where a == 10
f <- function(y) {
a <- 5
eval_tidy(y) # Instead of simple eval()
}
f(x) # 15 = 10 + 5
Capturing an expression or a quosure can be moved to be inside the function by using the en-
versions of expr
and quo
:
f <- function(y) {
a <- 5
eval(rlang::enexpr(y))
}
g <- function(y) {
a <- 5
eval_tidy(rlang::enquo(y))
}
allowing users to pass expressions directly to the function
a <- 10
f(a*4) # 20 = 5*4, because f captures expressions, and a is overwritten
g(a*4) # 40 = 10*4, because g captures quosures
And with all of the above said, {{x}}
is just a shorthand notation for !!enquo(x)
.
!!
means double- (and!!!
triple)-negation oflogical
operators.rlang
and other tidyverse packages have adopted it to be used for NSE evaluation of variables. – Ahlgren