Quasiquotation is the mechanism that makes it possible to program
flexibly with tidy evaluation grammars like dplyr. It is enabled in
all tidyeval quoting functions, the most fundamental of which are
quo()
and expr()
.
Quasiquotation is the combination of quoting an expression while allowing immediate evaluation (unquoting) of part of that expression. We provide both syntactic operators and functional forms for unquoting.
The !!
operator unquotes its argument. It gets evaluated
immediately in the surrounding context.
The !!!
operator unquotes and splices its argument. The
argument should represent a list or a vector. Each element will
be embedded in the surrounding call, i.e. each element is
inserted as an argument. If the vector is named, the names are
used as argument names.
If the vector is a classed object (like a factor), it is
converted to a list with base::as.list()
to ensure proper
dispatch. If it is an S4 objects, it is converted to a list with
methods::as()
.
Use qq_show()
to experiment with quasiquotation or debug the
effect of unquoting operators. qq_show()
quotes its input,
processes unquoted parts, and prints the result with
expr_print()
. This expression printer has a clearer output than
the base R printer (see the documentation topic).
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 quoted, 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 is right up the
alley for the unquoting operator !!
. If you were able to unquote
the variable when supplying the name, the argument would be named
after the content of that variable.
Unfortunately R is very strict about the kind of expressions
supported on the LHS of =
. This is why we have made the more
flexible :=
operator 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 unquote on the LHS:
name <- "Jane" list2(!!name := 1 + 2) exprs(!!name := 1 + 2) quos(!!name := 1 + 2)
Like =
, the :=
operator expects strings or symbols on its LHS.
Note that unquoting on the LHS of :=
only works in top level
expressions. These are all valid:
exprs(!!nm := x) tibble(!!nm := x) list2(!!nm := x)
But deep-unquoting names isn't supported:
expr(foo(!!nm := x)) exprs(foo(!!nm := x))
Formally, quo()
and expr()
are quasiquote functions, !!
is
the unquote operator, and !!!
is the unquote-splice operator.
These terms have a rich history in Lisp languages, and live on in
modern languages like
Julia
and
Racket.
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 operates 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.
#> how_many(this)#> how_many(this)#> <quosure> #> expr: ^how_many(this) #> env: 0x9063b00# 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))#> how_many(apples)#> how_many(oranges)#> how_many(3)#> <quosure> #> expr: ^how_many(3) #> env: 0x9063b00# 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)))#> how_many(1:10)#> <quosure> #> expr: ^how_many(<int: 1L, 2L, 3L, 4L, 5L, ...>) #> env: 0x9063b00# 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)))#> how_many(<int: 1L, 2L, 3L, 4L, 5L, ...>)# 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))#> <quosure> #> expr: ^mean(<int: 1L, 2L, 3L>, na.rm = TRUE) #> env: 0x9063b00# You can combine the two var <- quote(xyz) extra_args <- list(trim = 0.9, na.rm = TRUE) quo(mean(!!var , !!!extra_args))#> <quosure> #> expr: ^mean(xyz, trim = 0.9, na.rm = TRUE) #> env: 0x9063b00# The plural versions have support for the `:=` operator. # Like `=`, `:=` creates named arguments: quos(mouse1 := bernard, mouse2 = bianca)#> <list_of<quosure>> #> #> $mouse1 #> <quosure> #> expr: ^bernard #> env: 0x9063b00 #> #> $mouse2 #> <quosure> #> expr: ^bianca #> env: 0x9063b00 #># The `:=` is mainly useful to unquote names. Unlike `=` it # supports `!!` on its LHS: var <- "unquote me!" quos(!!var := bernard, mouse2 = bianca)#> <list_of<quosure>> #> #> $`unquote me!` #> <quosure> #> expr: ^bernard #> env: 0x9063b00 #> #> $mouse2 #> <quosure> #> expr: ^bianca #> env: 0x9063b00 #># All these features apply to dots captured by enquos(): fn <- function(...) enquos(...) fn(!!!args, !!var := penny)#> <list_of<quosure>> #> #> [[1]] #> <quosure> #> expr: ^<int: 1L, 2L, 3L> #> env: empty #> #> $na.rm #> <quosure> #> expr: ^TRUE #> env: empty #> #> $`unquote me!` #> <quosure> #> expr: ^penny #> env: 0x9063b00 #># Unquoting is especially useful for building an expression by # expanding around a variable part (the unquoted part): quo1 <- quo(toupper(foo)) quo1#> <quosure> #> expr: ^toupper(foo) #> env: 0x9063b00#> <quosure> #> expr: ^paste(^toupper(foo), bar) #> env: 0x9063b00#> <quosure> #> expr: ^list(^paste(^toupper(foo), bar), a, b, c, d, e) #> env: 0x9063b00