R: Defuse R expressions

nse-defuse {rlang} | R Documentation

## Defuse R expressions

### Description

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 `!!!`.

### Usage

    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.

### 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()`.

### Examples

    # expr() and exprs() capture expressions that you supply:
    expr(symbol)
    exprs(several, such, symbols)

    # enexpr() and enexprs() capture expressions that your user supplied:
    expr_inputs <- function(arg, ...) {
      user_exprs <- enexprs(arg, ...)
      user_exprs
    }
    expr_inputs(hello)
    expr_inputs(hello, bonjour, 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")
    ## sym_inputs(say(hello))  # Error: Must supply symbols or strings
    expr_inputs(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))
    expr(say(!!what))

    # 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)

    # Finally, you can capture expressions as quosures. A quosure is an
    # object that contains both the expression and its environment:
    quo <- quo(letters)
    quo

    get_expr(quo)
    get_env(quo)

    # Quosures can be evaluated with eval_tidy():
    eval_tidy(quo)

    # 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

    # 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)

[Package rlang version 0.4.5 Index]