Quosures are quoted expressions that keep track of an environment (just like closurefunctions). They are implemented as a subclass of one-sided formulas. They are an essential piece of the tidy evaluation framework.
quo()
quotes its input (i.e. captures R code without
evaluation), captures the current environment, and bundles them
in a quosure.
enquo()
takes a symbol referring to a function argument, quotes
the R code that was supplied to this argument, captures the
environment where the function was called (and thus where the R
code was typed), and bundles them in a quosure.
quos()
is a bit different to other functions as it returns a
list of quosures. You can supply several expressions directly,
e.g. quos(foo, bar)
, but more importantly you can also supply
dots: quos(...)
. In the latter case, expressions forwarded
through dots are captured and transformed to quosures. The
environments bundled in those quosures are the ones where the
code was supplied as arguments, even if the dots were forwarded
multiple times across several function calls.
new_quosure()
is the only constructor that takes its arguments
by value. It lets you create a quosure from an expression and an
environment.
quo(expr)new_quosure(expr, env = caller_env())
enquo(arg)
An expression.
An environment specifying the lexical enclosure of the quosure.
A symbol referring to an argument. The expression supplied to that argument will be captured unevaluated.
A formula whose right-hand side contains the quoted expression supplied as argument.
Quosures play an essential role thanks to these features:
They allow consistent scoping of quoted expressions by recording an expression along with its local environment.
quo()
, quos()
and enquo()
all support quasiquotation. By
unquoting other quosures, you can safely combine expressions even
when they come from different contexts. You can also unquote
values and raw expressions depending on your needs.
Unlike formulas, quosures self-evaluate (see eval_tidy()
)
within their own environment, which is why you can unquote a
quosure inside another quosure and evaluate it like you've
unquoted a raw expression.
See the programming withdplyr vignette for practical examples. For developers, the tidyevaluation vignette provides an overview of this approach. The quasiquotation page goes in detail over the unquoting and splicing operators.
expr()
for quoting a raw expression with quasiquotation.
The quasiquotation page goes over unquoting and splicing.
# NOT RUN {
# quo() is a quotation function just like expr() and quote():
expr(mean(1:10 * 2))
quo(mean(1:10 * 2))
# It supports quasiquotation and allows unquoting (evaluating
# immediately) part of the quoted expression:
quo(mean(!! 1:10 * 2))
# What makes quo() often safer to use than quote() and expr() is
# that it keeps track of the contextual environment. This is
# especially important if you're referring to local variables in
# the expression:
var <- "foo"
quo <- quo(var)
quo
# Here `quo` quotes `var`. Let's check that it also captures the
# environment where that symbol is defined:
identical(get_env(quo), get_env())
env_has(quo, "var")
# Keeping track of the environment is important when you quote an
# expression in a context (that is, a particular function frame)
# and pass it around to other functions (which will be run in their
# own evaluation frame):
fn <- function() {
foobar <- 10
quo(foobar * 2)
}
quo <- fn()
quo
# `foobar` is not defined here but was defined in `fn()`'s
# evaluation frame. However, the quosure keeps track of that frame
# and is safe to evaluate:
eval_tidy(quo)
# Like other formulas, quosures are normally self-quoting under
# evaluation:
eval(~var)
eval(quo(var))
# But eval_tidy() evaluates expressions in a special environment
# (called the overscope) where they become promises. They
# self-evaluate under evaluation:
eval_tidy(~var)
eval_tidy(quo(var))
# Note that it's perfectly fine to unquote quosures within
# quosures, as long as you evaluate with eval_tidy():
quo <- quo(letters)
quo <- quo(toupper(!! quo))
quo
eval_tidy(quo)
# Quoting as a quosure is necessary to preserve scope information
# and make sure objects are looked up in the right place. However,
# there are situations where it can get in the way. This is the
# case when you deal with non-tidy NSE functions that do not
# understand formulas. You can inline the RHS of a formula in a
# call thanks to the UQE() operator:
nse_function <- function(arg) substitute(arg)
var <- locally(quo(foo(bar)))
quo(nse_function(UQ(var)))
quo(nse_function(UQE(var)))
# This is equivalent to unquoting and taking the RHS:
quo(nse_function(!! get_expr(var)))
# One of the most important old-style NSE function is the dollar
# operator. You need to use UQE() for subsetting with dollar:
var <- quo(cyl)
quo(mtcars$UQE(var))
# `!!`() is also treated as a shortcut. It is meant for situations
# where the bang operator would not parse, such as subsetting with
# $. Since that's its main purpose, we've made it a shortcut for
# UQE() rather than UQ():
var <- quo(cyl)
quo(mtcars$`!!`(var))
# When a quosure is printed in the console, the brackets indicate
# if the enclosure is the global environment or a local one:
locally(quo(foo))
# Literals are enquosed with the empty environment because they can
# be evaluated anywhere. The brackets indicate "empty":
quo(10L)
# To differentiate local environments, use str(). It prints the
# machine address of the environment:
quo1 <- locally(quo(foo))
quo2 <- locally(quo(foo))
quo1; quo2
str(quo1); str(quo2)
# You can also see this address by printing the environment at the
# console:
get_env(quo1)
get_env(quo2)
# new_quosure() takes by value an expression that is already quoted:
expr <- quote(mtcars)
env <- as_env("datasets")
quo <- new_quosure(expr, env)
quo
eval_tidy(quo)
# }
Run the code above in your browser using DataLab