Stable lifecycle

The defusing operators expr() and enquo() prevent the evaluation of R code. Defusing is also known as quoting, and is done in base R by quote() and substitute(). When a function argument is defused, R doesn't return its value like it normally would but it returns the R expression describing how to make the value. These defused expressions are like blueprints for computing values.

There are two main ways to defuse expressions, to which correspond the two functions expr() and enquo(). Whereas expr() defuses your own expression, enquo() defuses expressions supplied as argument by the user of a function. See section on function arguments for more on this distinction.

The main purpose of defusing evaluation of an expression is to enable data-masking, where an expression is evaluated in the context of a data frame so that you can write var instead of data$var. The expression is defused so it can be resumed later on, in a context where the data-variables have been defined.

Defusing prevents the evaluation of R code, but you can still force evaluation inside a defused expression with the forcing operators !! and !!!.

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 as_label() to build a default 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.

Types of defused expressions

  • Calls, like f(1, 2, 3) or 1 + 1 represent the action of calling a function to compute a new value, such as a vector.

  • Symbols, like x or df, represent named objects. When the object pointed to by the symbol was defined in a function or in the global environment, we call it an environment-variable. When the object is a column in a data frame, we call it a data-variable.

You can create new call or symbol objects by using the defusing function expr():

# Create a symbol representing objects called `foo`
expr(foo)

# Create a call representing the computation of the mean of `foo`
expr(mean(foo, na.rm = TRUE))

Defusing is not the only way to create defused expressions. You can also assemble them from data:

# Assemble a symbol from a string
var <- "foo"
sym(var)

# Assemble a call from strings, symbols, and other objects
call("mean", sym(var), na.rm = TRUE)

Defusing function arguments

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

  • You can defuse expressions that you supply with expr(). This is one way of creating symbols and calls (see previous section).

  • You can defuse the expressions supplied by the user of your function with the operators starting with en like ensym(), enquo() and their plural variants. They defuse function arguments .

Defused arguments and quosures

If you inspect the return values of expr() and enquo(), you'll notice that the latter doesn't return a raw expression like the former. Instead it returns a quosure, a wrapper containing an expression and an environment. R needs information about the environment to properly evaluate the argument expression because it comes from a different context than the current function.

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

Comparison to base R

  • The defusing operator expr() is similar to quote(). Like bquote(), it allows forcing evaluation of parts of an expression.

  • The plural variant exprs() is similar to alist().

  • The argument-defusing operator enquo() is similar to substitute().

See also

enquo0() and enquos0() for variants that do not perform automatic injection/unquotation.

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:  0x560eaab58338

get_expr(quo)
#> letters
get_env(quo)
#> <environment: 0x560eaab58338>

# 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:  0x560eb0078830

# 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