Stable lifecycle

Quotation is a mechanism by which an expression supplied as argument is captured by a function. Instead of seeing the value of the argument, the function sees the recipe (the R code) to make that value. This is possible because R expressions are representable as regular objects in R:

  • Calls represent the action of calling a function to compute a new value. Evaluating a call causes that value to be computed. Calls typically involve symbols to reference R objects.

  • Symbols represent the name that is given to an object in a particular context (an environment).

We call objects containing calls and symbols expressions. There are two ways to create R expressions. First you can build calls and symbols from parts and pieces (see sym(), syms() and call2()). The other way is by quotation or quasiquotation, i.e. by intercepting an expression instead of evaluating it.

expr(expr)

enexpr(arg)

exprs(..., .named = FALSE, .ignore_empty = c("trailing", "none",
  "all"), .unquote_names = TRUE)

enexprs(..., .named = FALSE, .ignore_empty = c("trailing", "none",
  "all"), .unquote_names = TRUE, .homonyms = c("keep", "first", "last",
  "error"), .check_assign = FALSE)

ensym(arg)

ensyms(..., .named = FALSE, .ignore_empty = c("trailing", "none",
  "all"), .unquote_names = TRUE, .homonyms = c("keep", "first", "last",
  "error"), .check_assign = FALSE)

quo(expr)

enquo(arg)

quos(..., .named = FALSE, .ignore_empty = c("trailing", "none", "all"),
  .unquote_names = TRUE)

enquos(..., .named = FALSE, .ignore_empty = c("trailing", "none",
  "all"), .unquote_names = TRUE, .homonyms = c("keep", "first", "last",
  "error"), .check_assign = FALSE)

Arguments

expr

An expression.

arg

A symbol representing an argument. The expression supplied to that argument will be captured instead of being evaluated.

...

For enexprs(), ensyms() and enquos(), names of arguments to capture without evaluation (including ...). For exprs() and quos(), the expressions to capture unevaluated (including expressions contained in ...).

.named

Whether to ensure all dots are named. Unnamed elements are processed with quo_name() to build a default name. See also quos_auto_name().

.ignore_empty

Whether to ignore empty arguments. Can be one of "trailing", "none", "all". If "trailing", only the last argument is ignored if it is empty. Note that "trailing" applies only to arguments passed in ..., not to named arguments. On the other hand, "all" also applies to named arguments.

.unquote_names

Whether to treat := as =. Unlike =, the := syntax supports !! unquoting on the LHS.

.homonyms

How to treat arguments with the same name. The default, "keep", preserves these arguments. Set .homonyms to "first" to only keep the first occurrences, to "last" to keep the last occurrences, and to "error" to raise an informative error and indicate what arguments have duplicated names.

.check_assign

Whether to check for <- calls passed in dots. When TRUE and a <- call is detected, a warning is issued to advise users to use = if they meant to match a function parameter, or wrap the <- call in braces otherwise. This ensures assignments are explicit.

User expressions versus your expressions

There are two points of view when it comes to capturing an expression:

  • You can capture the expressions supplied by the user of your function. This is the purpose of ensym(), enexpr() and enquo() and their plural variants. These functions take an argument name and capture the expression that was supplied to that argument.

  • You can capture the expressions that you supply. To this end use expr() and quo() and their plural variants exprs() and quos().

Capture raw expressions

  • enexpr() and expr() capture a single raw expression.

  • enexprs() and exprs() capture a list of raw expressions including expressions contained in ....

  • ensym() and ensyms() are variants of enexpr() and enexprs() that check the captured expression is either a string (which they convert to symbol) or a symbol. If anything else is supplied they throw an error.

In terms of base functions, enexpr(arg) corresponds to base::substitute(arg) (though that function also features complex substitution semantics) and expr() is like quote() (and bquote() if we consider unquotation syntax). The plural variant exprs() is equivalent to base::alist(). Finally there is no function in base R that is equivalent to enexprs() but you can reproduce its behaviour with eval(substitute(alist(...))).

Capture expressions in quosures

quo() and enquo() are similar to their expr counterparts but capture both the expression and its environment in an object called a quosure. This wrapper contains a reference to the original environment in which that expression was captured. Keeping track of the environments of expressions is important because this is where functions and objects mentioned in the expression are defined.

Quosures are objects that can be evaluated with eval_tidy() just like symbols or function calls. Since they always evaluate in their original environment, quosures can be seen as vehicles that allow expressions to travel from function to function but that beam back instantly to their original environment upon evaluation.

See the quosure help topic about tools to work with quosures.

Quasiquotation

All quotation functions in rlang have support for unquoting operators. The combination of quotation and unquotation is called quasiquotation.

Unquotation provides a way to refer to variables during quotation. Variables are problematic when quoting because a captured expression is essentially a constant, just like a string is a constant. For instance in all the following cases apple is a constant: ~apple, "apple" and expr(apple). Unquoting allows you to introduce a part of variability within a captured expression.

  • In the case of enexpr() and enquo(), unquoting provides an escape hatch to the users of your function that allows them to manipulate the expression that you capture.

  • In the case of expr() and quo(), quasiquotation lets you build a complex expressions where some parts are constant (the parts that are captured) and some parts are variable (the parts that are unquoted).

See the quasiquotation help topic for more about this as well as the chapter in Advanced R.

Examples

# expr() and exprs() capture expressions that you supply: expr(symbol)
#> symbol
exprs(several, such, symbols)
#> [[1]] #> several #> #> [[2]] #> such #> #> [[3]] #> symbols #>
# enexpr() and enexprs() capture expressions that your user supplied: expr_inputs <- function(arg, ...) { user_exprs <- enexprs(arg, ...) user_exprs } expr_inputs(hello)
#> [[1]] #> hello #>
expr_inputs(hello, bonjour, ciao)
#> [[1]] #> hello #> #> [[2]] #> bonjour #> #> [[3]] #> ciao #>
# ensym() and ensyms() provide additional type checking to ensure # the user calling your function has supplied bare object names: sym_inputs <- function(...) { user_symbols <- ensyms(...) user_symbols } sym_inputs(hello, "bonjour")
#> [[1]] #> hello #> #> [[2]] #> bonjour #>
## sym_inputs(say(hello)) # Error: Must supply symbols or strings expr_inputs(say(hello))
#> [[1]] #> say(hello) #>
# All these quoting functions have quasiquotation support. This # means that you can unquote (evaluate and inline) part of the # captured expression: what <- sym("bonjour") expr(say(what))
#> say(what)
expr(say(!!what))
#> say(bonjour)
# This also applies to expressions supplied by the user. This is # like an escape hatch that allows control over the captured # expression: expr_inputs(say(!!what), !!what)
#> [[1]] #> say(bonjour) #> #> [[2]] #> bonjour #>
# Finally, you can capture expressions as quosures. A quosure is an # object that contains both the expression and its environment: quo <- quo(letters) quo
#> <quosure> #> expr: ^letters #> env: 0x6c61110
get_expr(quo)
#> letters
get_env(quo)
#> <environment: 0x6c61110>
# Quosures can be evaluated with eval_tidy(): eval_tidy(quo)
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" #> [20] "t" "u" "v" "w" "x" "y" "z"
# They have the nice property that you can pass them around from # context to context (that is, from function to function) and they # still evaluate in their original environment: multiply_expr_by_10 <- function(expr) { # We capture the user expression and its environment: expr <- enquo(expr) # Then create an object that only exists in this function: local_ten <- 10 # Now let's create a multiplication expression that (a) inlines # the user expression as LHS (still wrapped in its quosure) and # (b) refers to the local object in the RHS: quo(!!expr * local_ten) } quo <- multiply_expr_by_10(2 + 3) # The local parts of the quosure are printed in colour if your # terminal is capable of displaying colours: quo
#> <quosure> #> expr: ^(^2 + 3) * local_ten #> env: 0x43b9c60
# All the quosures in the expression evaluate in their original # context. The local objects are looked up properly and we get the # expected result: eval_tidy(quo)
#> [1] 50