Conditional pipelines

You can simplify a bit by using ..n notation to refer to the nth term of ...:

library(dplyr)

conditionally <- function(fun){
    function(..., execute) {
        if (execute) fun(...) else ..1
    }
}

mtcars %>% 
    conditionally(filter)(hp == 245, execute = TRUE) %>% 
    conditionally(select)(cyl, execute = FALSE)
#>    mpg cyl disp  hp drat   wt  qsec vs am gear carb
#> 1 14.3   8  360 245 3.21 3.57 15.84  0  0    3    4
#> 2 13.3   8  350 245 3.73 3.84 15.41  0  0    3    4

Without writing your own adverbial function, you can do this inline with braces to control where the incoming data . is passed:

mtcars %>% 
    {if (TRUE) filter(., hp == 245) else .} %>% 
    {if (FALSE) select(., cyl) else .}
#>    mpg cyl disp  hp drat   wt  qsec vs am gear carb
#> 1 14.3   8  360 245 3.21 3.57 15.84  0  0    3    4
#> 2 13.3   8  350 245 3.73 3.84 15.41  0  0    3    4

With a little rlang magic, you can write a version of conditionally that does the same thing, even taking raw expressions for the condition and call and evaluating each in the context of the data:

provided <- function(data, condition, call) {
    if (rlang::eval_tidy(enquo(condition), data)) {
        rlang::eval_tidy(rlang::quo_squash(quo(data %>% !!enquo(call))))
    } else data
}

mtcars %>% 
    provided(all(cyl > 0), filter(hp == 245)) %>% 
    provided(any(cyl < 0), select(cyl))
#>    mpg cyl disp  hp drat   wt  qsec vs am gear carb
#> 1 14.3   8  360 245 3.21 3.57 15.84  0  0    3    4
#> 2 13.3   8  350 245 3.73 3.84 15.41  0  0    3    4

As far as API structure, I worry that implicitly passing . into the call parameter is inconsistent with the pipe's expected behavior, but the code does read nicely.

18 Likes