Force parts of an expression
It is sometimes useful to force early evaluation of part of an expression before it gets fully evaluated. The tidy eval framework provides several forcing operators for different use cases.
The bang-bang operator !!
forces a single object. One
common case for !!
is to substitute an environment-variable
(created with <-
) with a data-variable (inside a data frame).
library(dplyr) # The environment variable `var` refers to the data-variable # `height` var <- sym("height") # We force `var`, which substitutes it with `height` starwars %>% summarise(avg = mean(!!var, na.rm = TRUE))
The big-bang operator !!!
forces-splice a list of objects.
The elements of the list are spliced in place, meaning that they
each become one single argument.
vars <- syms(c("height", "mass")) # Force-splicing is equivalent to supplying the elements separately starwars %>% select(!!!vars) starwars %>% select(height, mass)
The curly-curly operator {{ }}
for function arguments is a bit
special because it forces the function argument and immediately
defuses it. The defused expression is substituted in place, ready
to be evaluated in another context, such as the data frame.
In practice, this is useful when you have a data-variable in an env-variable (such as a function argument).
# Force-defuse all function arguments that might contain # data-variables by embracing them with {{ }} mean_by <- function(data, by, var) { data %>% group_by({{ by }}) %>% summarise(avg = mean({{ var }}, na.rm = TRUE)) } # The env-variables `by` and `var` are forced but defused. # The data-variables they contain are evaluated by dplyr later on # in data context. iris %>% mean_by(by = Species, var = Sepal.Width)
Use qq_show()
to experiment with forcing operators. qq_show()
defuses its input, processes all forcing operators, and prints the
result with expr_print()
to reveal objects inlined in the
expression by the forcing operators.
qq_show(expr)
expr |
An expression to be quasiquoted. |
When a function takes multiple named arguments
(e.g. dplyr::mutate()
), it is difficult to supply a variable as
name. Since the LHS of =
is defused, giving the name
of a variable results in the argument having the name of the
variable rather than the name stored in that variable. This problem
of forcing evaluation of names is exactly what the !!
operator is
for.
Unfortunately R is very strict about the kind of expressions
supported on the LHS of =
. This is why rlang interprets the
walrus operator :=
as an alias of =
. You can use it to supply
names, e.g. a := b
is equivalent to a = b
. Since its syntax is
more flexible you can also force names on its LHS:
name <- "Jane" list2(!!name := 1 + 2) exprs(!!name := 1 + 2)
Like =
, the :=
operator expects strings or symbols on its LHS.
Since unquoting names is related to interpolating within a string
with the glue package, we have made the glue syntax available on
the LHS of :=
:
list2("{name}" := 1) tibble("{name}" := 1)
You can also interpolate defused function arguments with double
braces {{
, similar to the curly-curly syntax:
wrapper <- function(data, var) { data %>% mutate("{{ var }}_foo" := {{ var }} * 2) }
Currently, forcing names with :=
only works in top level
expressions. These are all valid:
exprs("{name}" := x) tibble("{name}" := x)
But deep-forcing names isn't supported:
exprs(this(is(deep("{name}" := x))))
Calling UQ()
and UQS()
with the rlang namespace qualifier is
deprecated as of rlang 0.3.0. Just use the unqualified forms
instead:
# Bad rlang::expr(mean(rlang::UQ(var) * 100)) # Ok rlang::expr(mean(UQ(var) * 100)) # Good rlang::expr(mean(!!var * 100))
Supporting namespace qualifiers complicates the implementation of unquotation and is misleading as to the nature of unquoting operators (which are syntactic operators that operate at quotation-time rather than function calls at evaluation-time).
UQ()
and UQS()
were soft-deprecated in rlang 0.2.0 in order
to make the syntax of quasiquotation more consistent. The prefix
forms are now `!!`()
and `!!!`()
which is
consistent with other R operators (e.g. `+`(a, b)
is the
prefix form of a + b
).
Note that the prefix forms are not as relevant as before because
!!
now has the right operator precedence, i.e. the same as
unary -
or +
. It is thus safe to mingle it with other
operators, e.g. !!a + !!b
does the right thing. In addition the
parser now strips one level of parentheses around unquoted
expressions. This way (!!"foo")(...)
expands to foo(...)
.
These changes make the prefix forms less useful.
Finally, the named functional forms UQ()
and UQS()
were
misleading because they suggested that existing knowledge about
functions is applicable to quasiquotation. This was reinforced by
the visible definitions of these functions exported by rlang and
by the tidy eval parser interpreting rlang::UQ()
as !!
. In
reality unquoting is not a function call, it is a syntactic
operation. The operator form makes it clearer that unquoting is
special.
# Interpolation with {{ }} is the easiest way to forward # arguments to tidy eval functions: if (is_attached("package:dplyr")) { # Forward all arguments involving data frame columns by # interpolating them within other data masked arguments. # Here we interpolate `arg` in a `summarise()` call: my_function <- function(data, arg) { summarise(data, avg = mean({{ arg }}, na.rm = TRUE)) } my_function(mtcars, cyl) my_function(mtcars, cyl * 10) # The operator is just a shortcut for `!!enquo()`: my_function <- function(data, arg) { summarise(data, avg = mean(!!enquo(arg), na.rm = TRUE)) } my_function(mtcars, cyl) } # Quasiquotation functions quote expressions like base::quote() quote(how_many(this)) expr(how_many(this)) quo(how_many(this)) # In addition, they support unquoting. Let's store symbols # (i.e. object names) in variables: this <- sym("apples") that <- sym("oranges") # With unquotation you can insert the contents of these variables # inside the quoted expression: expr(how_many(!!this)) expr(how_many(!!that)) # You can also insert values: expr(how_many(!!(1 + 2))) quo(how_many(!!(1 + 2))) # Note that when you unquote complex objects into an expression, # the base R printer may be a bit misleading. For instance compare # the output of `expr()` and `quo()` (which uses a custom printer) # when we unquote an integer vector: expr(how_many(!!(1:10))) quo(how_many(!!(1:10))) # This is why it's often useful to use qq_show() to examine the # result of unquotation operators. It uses the same printer as # quosures but does not return anything: qq_show(how_many(!!(1:10))) # Use `!!!` to add multiple arguments to a function. Its argument # should evaluate to a list or vector: args <- list(1:3, na.rm = TRUE) quo(mean(!!!args)) # You can combine the two var <- quote(xyz) extra_args <- list(trim = 0.9, na.rm = TRUE) quo(mean(!!var , !!!extra_args)) # The plural versions have support for the `:=` operator. # Like `=`, `:=` creates named arguments: quos(mouse1 := bernard, mouse2 = bianca) # The `:=` is mainly useful to unquote names. Unlike `=` it # supports `!!` on its LHS: var <- "unquote me!" quos(!!var := bernard, mouse2 = bianca) # All these features apply to dots captured by enquos(): fn <- function(...) enquos(...) fn(!!!args, !!var := penny) # Unquoting is especially useful for building an expression by # expanding around a variable part (the unquoted part): quo1 <- quo(toupper(foo)) quo1 quo2 <- quo(paste(!!quo1, bar)) quo2 quo3 <- quo(list(!!quo2, !!!syms(letters[1:5]))) quo3
Please choose more modern alternatives, such as Google Chrome or Mozilla Firefox.