% 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()}} } }