% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/nse-inject.R
\name{splice-operator}
\alias{splice-operator}
\alias{!!!}
\title{Splice operator \verb{!!!}}
\description{
The splice operator \verb{!!!} implemented in \link[=dyn-dots]{dynamic dots}
injects a list of arguments into a function call. It belongs to the
family of \link[=topic-inject]{injection} operators and provides the same
functionality as \code{\link[=do.call]{do.call()}}.
The two main cases for splice injection are:
\itemize{
\item Turning a list of inputs into distinct arguments. This is
especially useful with functions that take data in \code{...}, such as
\code{\link[base:cbind]{base::rbind()}}.
\if{html}{\out{
}}\preformatted{dfs <- list(mtcars, mtcars)
inject(rbind(!!!mtcars))
}\if{html}{\out{
}}
\item Injecting \link[=topic-defuse]{defused expressions} like
\link[=sym]{symbolised} column names.
For tidyverse APIs, this second case is no longer as useful
since dplyr 1.0 and the \code{across()} operator.
}
}
\section{Where does \verb{!!!} work?}{
\verb{!!!} does not work everywhere, you can only use it within certain
special functions:
\itemize{
\item Functions taking \link[=dyn-dots]{dynamic dots} like \code{\link[=list2]{list2()}}.
\item Functions taking \link[=topic-defuse]{defused} and
\link[=topic-data-mask]{data-masked} arguments, which are dynamic by
default.
\item Inside \code{\link[=inject]{inject()}}.
}
Most tidyverse functions support \verb{!!!} out of the box. With base
functions you need to use \code{\link[=inject]{inject()}} to enable \verb{!!!}.
Using the operator out of context may lead to incorrect results,
see \ifelse{html}{\link[=topic-inject-out-of-context]{What happens if I use injection operators out of context?}}{\link[=topic-inject-out-of-context]{What happens if I use injection operators out of context?}}.
}
\section{Splicing a list of arguments}{
Take a function like \code{\link[base:cbind]{base::rbind()}} that takes data in \code{...}. This
sort of functions takes a variable number of arguments.
\if{html}{\out{}}\preformatted{df1 <- data.frame(x = 1)
df2 <- data.frame(x = 2)
rbind(df1, df2)
#> x
#> 1 1
#> 2 2
}\if{html}{\out{
}}
Passing individual arguments is only possible for a fixed amount of
arguments. When the arguments are in a list whose length is
variable (and potentially very large), we need a programmatic
approach like the splicing syntax \verb{!!!}:
\if{html}{\out{}}\preformatted{dfs <- list(df1, df2)
inject(rbind(!!!dfs))
#> x
#> 1 1
#> 2 2
}\if{html}{\out{
}}
Because \code{rbind()} is a base function we used \code{\link[=inject]{inject()}} to
explicitly enable \verb{!!!}. However, many functions implement \link[=list2]{dynamic dots} with \verb{!!!} implicitly enabled out of the box.
\if{html}{\out{}}\preformatted{tidyr::expand_grid(x = 1:2, y = c("a", "b"))
#> # A tibble: 4 x 2
#> x y
#>
#> 1 1 a
#> 2 1 b
#> 3 2 a
#> 4 2 b
xs <- list(x = 1:2, y = c("a", "b"))
tidyr::expand_grid(!!!xs)
#> # A tibble: 4 x 2
#> x y
#>
#> 1 1 a
#> 2 1 b
#> 3 2 a
#> 4 2 b
}\if{html}{\out{
}}
Note how the expanded grid has the right column names. That's
because we spliced a \emph{named} list. Splicing causes each name of the
list to become an argument name.
\if{html}{\out{}}\preformatted{tidyr::expand_grid(!!!set_names(xs, toupper))
#> # A tibble: 4 x 2
#> X Y
#>
#> 1 1 a
#> 2 1 b
#> 3 2 a
#> 4 2 b
}\if{html}{\out{
}}
}
\section{Splicing a list of expressions}{
Another usage for \verb{!!!} is to inject \link[=topic-defuse]{defused expressions} into \link[=topic-data-mask]{data-masked}
dots. However this usage is no longer a common pattern for
programming with tidyverse functions and we recommend using other
patterns if possible.
First, instead of using the \link[=topic-data-mask-programming]{defuse-and-inject pattern} with \code{...}, you can simply pass
them on as you normally would. These two expressions are completely
equivalent:
\if{html}{\out{}}\preformatted{my_group_by <- function(.data, ...) \{
.data \%>\% dplyr::group_by(!!!enquos(...))
\}
# This equivalent syntax is preferred
my_group_by <- function(.data, ...) \{
.data \%>\% dplyr::group_by(...)
\}
}\if{html}{\out{
}}
Second, more complex applications such as \link[=topic-metaprogramming]{transformation patterns} can be solved with the \code{across()}
operation introduced in dplyr 1.0. Say you want to take the
\code{mean()} of all expressions in \code{...}. Before \code{across()}, you had to
defuse the \code{...} expressions, wrap them in a call to \code{mean()}, and
inject them in \code{summarise()}.
\if{html}{\out{}}\preformatted{my_mean <- function(.data, ...) \{
# Defuse dots and auto-name them
exprs <- enquos(..., .named = TRUE)
# Wrap the expressions in a call to `mean()`
exprs <- purrr::map(exprs, ~ call("mean", .x, na.rm = TRUE))
# Inject them
.data \%>\% dplyr::summarise(!!!exprs)
\}
}\if{html}{\out{
}}
It is much easier to use \code{across()} instead:
\if{html}{\out{}}\preformatted{my_mean <- function(.data, ...) \{
.data \%>\% dplyr::summarise(across(c(...), ~ mean(.x, na.rm = TRUE)))
\}
}\if{html}{\out{
}}
}
\section{Performance of injected dots and dynamic dots}{
Take this \link[=dyn-dots]{dynamic dots} function:
\if{html}{\out{}}\preformatted{n_args <- function(...) \{
length(list2(...))
\}
}\if{html}{\out{
}}
Because it takes dynamic dots you can splice with \verb{!!!} out of the
box.
\if{html}{\out{}}\preformatted{n_args(1, 2)
#> [1] 2
n_args(!!!mtcars)
#> [1] 11
}\if{html}{\out{
}}
Equivalently you could enable \verb{!!!} explicitly with \code{\link[=inject]{inject()}}.
\if{html}{\out{}}\preformatted{inject(n_args(!!!mtcars))
#> [1] 11
}\if{html}{\out{
}}
While the result is the same, what is going on under the hood is
completely different. \code{\link[=list2]{list2()}} is a dots collector that
special-cases \verb{!!!} arguments. On the other hand, \code{\link[=inject]{inject()}}
operates on the language and creates a function call containing as
many arguments as there are elements in the spliced list. If you
supply a list of size 1e6, \code{inject()} is creating one million
arguments before evaluation. This can be much slower.
\if{html}{\out{}}\preformatted{xs <- rep(list(1), 1e6)
system.time(
n_args(!!!xs)
)
#> user system elapsed
#> 0.009 0.000 0.009
system.time(
inject(n_args(!!!xs))
)
#> user system elapsed
#> 0.445 0.012 0.457
}\if{html}{\out{
}}
The same issue occurs when functions taking dynamic dots are called
inside a data-masking function like \code{dplyr::mutate()}. The
mechanism that enables \verb{!!!} injection in these arguments is the
same as in \code{inject()}.
}
\seealso{
\itemize{
\item \ifelse{html}{\link[=topic-inject]{Injecting with !!, !!!, and glue syntax}}{\link[=topic-inject]{Injecting with !!, !!!, and glue syntax}}
\item \code{\link[=inject]{inject()}}
\item \code{\link[=exec]{exec()}}
}
}