Are there any objective reasons for why pipe operators from the R package magrittr
, such as %>%
, should be avoided when I program packages in R?
More specifically, I want to know if using pipe operators might cause coding conflicts or (positively or negatively) affect performance. I am looking for specific, concrete examples of such cases.
What does the pipe do? The pipe operator, written as %>% , has been a longstanding feature of the magrittr package for R. It takes the output of one function and passes it into another function as an argument. This allows us to link a sequence of analysis steps.
%>% is called the forward pipe operator in R. It provides a mechanism for chaining commands with a new forward-pipe operator, %>%. This operator will forward a value, or the result of an expression, into the next function call/expression.
What is the Pipe Operator? The pipe operator is a special operational function available under the magrittr and dplyr package (basically developed under magrittr), which allows us to pass the result of one function/argument to the other one in sequence. It is generally denoted by symbol %>% in R Programming.
Pipes are an extremely useful tool from the magrittr package 1 that allow you to express a sequence of multiple operations. They can greatly simplify your code and make your operations more intuitive.
Like all advanced functions written in R, %>%
carries a lot of overhead, so don't use it in loops (this includes implicit loops, such as the *apply
family, or the per group loops in packages like dplyr
or data.table
). Here's an example:
library(magrittr)
x = 1:10
system.time({for(i in 1:1e5) identity(x)})
# user system elapsed
# 0.07 0.00 0.08
system.time({for(i in 1:1e5) x %>% identity})
# user system elapsed
# 15.39 0.00 16.68
Adding dependencies to a package shouldn't be taken too lightly. Speaking generally, every package that your package depends on is a risk for future maintenance whenever the dependency updates, or in case the dependency stops being maintained. It also makes it (slightly) harder for people to install your package - though only noticeably so in cases where an internet connection is unreliable or in some cases where some packages are more difficult to install on certain systems or hardware. But if someone wants to put your package on a thumb drive to install somewhere, they will also need to make sure they have all of your dependencies (and the dependencies of your dependencies...).
Base R and the default packages have a long history, and R-Core is very conscious of not introducing changes that will break downstream dependencies. magrittr
is much newer, looks like it was first up on CRAN in Feb 2014.
Practically speaking, magrittr
has been stable and seems like a low risk dependency. Especially if you are importing just %>%
and ignoring the more esoteric operators it provides (as is done by dplyr
, tidyr
, et al.) you are probably quite safe. Its popularity almost guarantees that even if its creator abandons it, someone will take over the maintenance.
Now in 2022 we've had a couple R releases featuring the base pipe |>
, so there's a nice alternative with 0 dependencies as long as you can run R version 4.1.0 or greater.
The piping paradigm inverts the apparent order of function application in comparison with "standard functional programming". Whether this has adverse consequences depends on the function semiotics (my original mispledding was intended to be 'semantics' but the spielchucker though I meant semiotics
and that seemed OK). I happen to think piping creates code that is less readable, but that is because I have trained my brain to look at coding from the "inside-out". Compare:
y <- func3 ( func2( func1( x) ) )
y <- x %>% func1 %>% func2 %>% func3
To my way of thinking the first one is more readable since the information "flows" outward (and consistently leftward) and ends up in the leftmost position y
, where as the information in the second one flows to the right and then "turns around and is sent to the left. The piping paradigm also allow argument-less function application which I think increases the potential for error. R programming with only positional parameter matching often produces error messages that are totally inscrutable, whereas disciplining yourself to always (or almost always) use argument names has the benefit of much more informative error messages.
My preference would have been for a piping paradigm that had a consistent direction:
y <- func3 %<% func2 %<% func1 %<% x
# Or
x %>% func1 %>% func2 %>% func3 -> y
And I think this was actually part of the original design of pkg-magrittr
which I believe included a 'left-pipe' as well as a 'right-pipe'. So this is probably a human-factors design issue. R has left to right associativity and the typical user of the dplyr/magrittr piping paradigm generally obeys that rule. I probably have stiff-brain syndrome, and all you young guys are probably the future, so you make your choice. I really do admire Hadley's goal of rationalizing data input and processing so that files and SQL servers are seen as generalized serial devices.
The example offered by David Robinson suggests that keeping track of arguments is a big issues and I agree completely. My usual approach is using tabs and spaces to highlight the hierarchy:
func3 ( func2(
func1(x, a), # think we need an extra comma here
b, c), # and here
d, e, f)
x %>% func1(a) %>% func2(b, c) %>% func3(d, e, f)
Admittedly this is made easier with a syntax-aware editor when checking for missing commas or parentheses, but in the example above which was not done with one, the stacking/spacing method does highlight what I think was a syntax error. (I also quickly add argument names when having difficulties, but I think that would be equally applicable to piping code tactics.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With