tidyverse / magrittr

Improve the readability of R code with the pipe
https://magrittr.tidyverse.org
Other
957 stars 157 forks source link

Sequential pipe operator? #247

Closed ggrothendieck closed 2 years ago

ggrothendieck commented 3 years ago

Consider this example taken from SO, https://stackoverflow.com/questions/68131615/order-of-messages-to-console-within-functions-when-piped/68132380#68132380

testdata <- data.frame(a=c(3,5,7,1,2),b=c(2,5,6,3,9))

foo_1 <- function(DF){
  message("Hello 1")
  Obj <- DF %>% 
    mutate(Test_1=a*b)
  message("Bye 1")
  return(Obj)
} 

foo_2 <- function(DF){
  message("Hello 2")

  Obj <- DF %>% 
    mutate(Test_2=Test_1*2)
  message("Bye 2")
  return(Obj)
}

foo_3 <- function(DF){
  message("Hello 3")
  Obj <- DF %>% 
    mutate(Test_3=Test_2/a)

  message("Bye 3")
  return(Obj)
}

testdata %>% 
  foo_1() %>% 
  foo_2() %>% 
  foo_3()

The result of the above is that the message appear out of order. If we use the following then they appear in order.

testdata %>% 
  foo_1 %>% 
  { flush.console(); foo_2(.) } %>%
  { flush.console(); foo_3(.) }

Some way to address this would be desirable. %>% could issue a flush.console() or there could be a separate type of pipe that does that or there could be an option to control this.

lionel- commented 3 years ago

This is to be expected because of the lazy evaluation of function arguments in R. If you need sequential evaluation, add force(DF) at the start of your functions. You will get the intended order of messages for both f(a) and a |> f(). See the magrittr 2.0 release notes for more information https://www.tidyverse.org/blog/2020/11/magrittr-2-0-is-here/

ggrothendieck commented 3 years ago

The point is that there are features that make sense to be added to pipes to gain feature advantages over pure function composition.

lionel- commented 3 years ago

If you don't want to force inputs and need sequential piping you can use:

`%>%` <- magrittr::pipe_eager_lexical
ggrothendieck commented 3 years ago

So it already exists. I certainly missed that. Maybe magrittr could predefine a pipe operator for it?

lionel- commented 3 years ago

yes maybe, if there is interest in this.

Visitors please feel free to reply or react to this thread if this is something that you need.

thuettel commented 3 years ago

Since I was the OP of the linked Stackoverflow-Post I'd definitely appreciate implementing it.

ggrothendieck commented 3 years ago

Here is another application of the magrittr sequential operator. This would not work if we used %>% See https://stackoverflow.com/questions/68792277/pipeable-assign-function-for-r-does-not-assign-anything/68792380#68792380

`%s>%` <- pipe_eager_lexical
f3 <- function(x) x %s>% assign("thirteen", .) %>% { . + thirteen }

if (exists("thirteen")) rm(thirteen)
f3(13)
## [1] 26
ggrothendieck commented 3 years ago

Seems this comes up repeatedly on SO. Here is another relevant question there: https://stackoverflow.com/questions/68858736/access-result-later-in-pipe/68859879#68859879

ggrothendieck commented 2 years ago

Another example: https://stackoverflow.com/questions/70848367/r-in-non-standardard-evaluation-nse-how-do-i-determine-the-right-calling-env/70850216#70850216