R: Injecting with !!, !!!, and glue syntax

topic-inject {rlang} | R Documentation

## Injecting with `!!`, `!!!`, and glue syntax

### Description

The injection operators are extensions of R implemented by rlang to modify a piece of code before R processes it. There are two main families:

- The dynamic dots operators, `!!!` and `"{"`.

- The metaprogramming operators `!!`, `{{`, and `"{{"`. Splicing with `!!!` can also be done in metaprogramming context.

### Dots injection

Unlike regular `...`, dynamic dots are programmable with injection operators.

#### Splicing with `!!!`

For instance, take a function like `rbind()` which takes data in `...`. To bind rows, you supply them as separate arguments:

    rbind(a = 1:2, b = 3:4)
    #>   [,1] [,2]
    #> a    1    2
    #> b    3    4

But how do you bind a variable number of rows stored in a list? The base R solution is to invoke `rbind()` with `do.call()`:

    rows <- list(a = 1:2, b = 3:4)

    do.call("rbind", rows)
    #>   [,1] [,2]
    #> a    1    2
    #> b    3    4

Functions that implement dynamic dots include a built-in way of folding a list of arguments in `...`. To illustrate this, we'll create a variant of `rbind()` that takes dynamic dots by collecting `...` with `list2()`:

    rbind2 <- function(...) {
      do.call("rbind", list2(...))
    }

It can be used just like `rbind()`:

    rbind2(a = 1:2, b = 3:4)
    #>   [,1] [,2]
    #> a    1    2
    #> b    3    4

And a list of arguments can be supplied by splicing the list with `!!!`:

    rbind2(!!!rows, c = 5:6)
    #>   [,1] [,2]
    #> a    1    2
    #> b    3    4
    #> c    5    6

#### Injecting names with `"{"`

A related problem comes up when an argument name is stored in a variable. With dynamic dots, you can inject the name using glue syntax with `"{"`:

    name <- "foo"

    rbind2("{name}" := 1:2, bar = 3:4)
    #>     [,1] [,2]
    #> foo    1    2
    #> bar    3    4

    rbind2("prefix_{name}" := 1:2, bar = 3:4)
    #>            [,1] [,2]
    #> prefix_foo    1    2
    #> bar           3    4

### Metaprogramming injection

Data-masked arguments support the following injection operators. They can also be explicitly enabled with `inject()`.

#### Embracing with `{{`

The embracing operator `{{` is made specially for function arguments. It defuses the expression supplied as argument and immediately injects it in place. The injected argument is then evaluated in another context such as a data mask.

    # Inject function arguments that might contain
    # data-variables by embracing them with {{ }}
    mean_by <- function(data, by, var) {
      data %>%
        dplyr::group_by({{ by }}) %>%
        dplyr::summarise(avg = mean({{ var }}, na.rm = TRUE))
    }

    # The data-variables `cyl` and `disp` inside the
    # env-variables `by` and `var` are injected inside `group_by()`
    # and `summarise()`
    mtcars %>% mean_by(by = cyl, var = disp)
    #> # A tibble: 3 x 2
    #>     cyl   avg
    #>   <dbl> <dbl>
    #> 1     4  105.
    #> 2     6  183.
    #> 3     8  353.

Learn more about this pattern in Data mask programming patterns.

#### Injecting with `!!`

Unlike `!!!` which injects a list of arguments, the injection operator `!!` (pronounced "bang-bang") injects a single object. One use case for `!!` is to substitute an environment-variable (created with `<-`) with a data-variable (inside a data frame).

    # The env-variable `var` contains a data-symbol object, in this
    # case a reference to the data-variable `height`
    var <- data_sym("disp")

    # We inject the data-variable contained in `var` inside `summarise()`
    mtcars %>%
      dplyr::summarise(avg = mean(!!var, na.rm = TRUE))
    #> # A tibble: 1 x 1
    #>     avg
    #>   <dbl>
    #> 1  231.

Another use case is to inject a variable by value to avoid name collisions.

    df <- data.frame(x = 1)

    # This name conflicts with a column in `df`
    x <- 100

    # Inject the env-variable
    df %>%
      dplyr::mutate(x = x / !!x)
    #>      x
    #> 1 0.01

Note that in most cases you don't need injection with `!!`. For instance, the `.data` and `.env` pronouns provide more intuitive alternatives to injecting a column name and injecting a value.

#### Splicing with `!!!`

The splice operator `!!!` of dynamic dots can also be used in metaprogramming context (inside data-masked arguments and inside `inject()`). For instance, we could reimplement the `rbind2()` function presented above using `inject()` instead of `do.call()`:

    rbind2 <- function(...) {
      inject(rbind(!!!list2(...)))
    }

There are two things going on here. We collect `...` with `list2()` so that the callers of `rbind2()` may use `!!!`. And we use `inject()` so that `rbind2()` itself may use `!!!` to splice the list of arguments passed to `rbind2()`.

### Injection in other languages

Injection is known as quasiquotation in other programming languages and in computer science. `expr()` is similar to a quasiquotation operator and `!!` is the unquote operator. These terms have a rich history in Lisp languages, and live on in modern languages like Julia and Racket. In base R, quasiquotation is performed with `bquote()`.

The main difference between rlang and other languages is that quasiquotation is often implicit instead of explicit. You can use injection operators in any defusing / quoting function (unless that function defuses its argument with a special operator like `enquo0()`). This is not the case in lisp languages for example where injection / unquoting is explicit and only enabled within a backquote.

### See also

- What happens if I use injection operators out of context?

[Package rlang version 1.0.2 Index]