4

I am trying to implement a pipeline that has an optional step which consists of a pipeline of several functions. It runs this pipeline based on a condition, and otherwise it just passes through the original value. However I've tried implementing this using if and also purrr::when, but in both cases the positive case simply returns the pipeline instead of executing it.

Here's a simple contrived example. I've simplified the conditional pipeline to only one function, but being able to use magrittr pipes inside the TRUE branch is important here.

library(magrittr)

maybe_round = function(x, round = TRUE){
  x %>%
    `if`(
      round,
      . %>% round(),
      .
    )
}
> maybe_round(5.3, TRUE)
Functional sequence with the following components:

 1. round(.)

Use 'functions' to extract the individual functions. 
> maybe_round(5.3, FALSE)
[1] 5.3

This second case is working correctly; it's returning the original value unchanged. But this first case is not, it's returning the magrittr pipeline, but not actually feeding it with 5. How can I get this to work as I intend? I assume this has something to do with how magrittr rewrites the syntax tree, but I can't quite work it out.

Migwell
  • 18,631
  • 21
  • 91
  • 160

3 Answers3

1

The syntax . %>% round(.) means function(.) round(.). Any time dot starts a pipeline it defines a function rather than being an ordinary pipeline. Put parentheses around the dot to prevent the dot from starting the inner pipeline.

 maybe_round = function(x, round = TRUE){
   x %>%
     `if`(
       round,
       (.) %>% round(),
       .
     )
 }

maybe_round(5.3, TRUE)
## [1] 5

Another possibility is to just leave it as a function and then evaluate that function at the outer dot like this:

 maybe_round = function(x, round = TRUE){
   x %>%
     `if`(
       round,
       (. %>% round())(.),
       .
     )
 }
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
1

This is unrelated to `if` — the same issue would manifest with any other function. In fact, the issue is the . %>% … expression, which is not a normal pipeline. Instead, this special syntax creates a lambda (see “Using the dot-place holder as lhsin the documentation).

If you insist on using a pipe here, you’ll need to first assign . to a different variable name, e.g.:

maybe_round = function(x, round = TRUE){
  x %>%
    `if`(
      round,
      {
          x = .
          x %>% round()
      },
      .
    )
}

… honestly, I would instead use a regular if expression and encapsulate it inside a function that can be piped into, i.e.

maybe_round = function (x, round = TRUE) {
    x %>% maybe_round_impl(round)
}

maybe_round_impl = function (x, round = TRUE) {
    if (round) {
        x %>% round()
    } else {
        x
    }
}
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
0

Just don't pipe again in the `if`().

maybe_round = function(x, round = TRUE){
  x %>%
    `if`(
      round,
      round(.),
      .
    )
}

maybe_round(5.3, TRUE)
# [1] 5
maybe_round(5.3, FALSE)
# [1] 5.3
jay.sf
  • 60,139
  • 8
  • 53
  • 110
  • That isn't really solving my problem though, I want to use pipes here because in reality it's a very long pipeline. This is just a simplified example. – Migwell Nov 16 '21 at 12:23
  • Seems like what you want to do, is to execute everything that is unrelated to the condition inside a pipe, assign to a variable. Then use if statements to complete the rest. If this is inside a custom function, have logical arguments which determine whether or not the condition is met. Or, post the longer code and we can give a solution. – jpenzer Nov 16 '21 at 12:44
  • I have intentionally simplified it because otherwise people will complain. You can trivially make the pipeline longer by just taking `. %>% round() %>% round() %>% round() %>% round()` as the `TRUE` case. – Migwell Nov 16 '21 at 13:09
  • I have shown a log of running it and you can see ti works proiperly. – G. Grothendieck Nov 16 '21 at 13:36