我应该避免使用管道运算符编写程序包吗?

29

是否存在客观原因,使得在编写R语言包时应避免使用来自R软件包magrittr的管道运算符pipe operators,例如%>%

更具体地说,我想知道使用管道运算符是否可能导致编码冲突或对性能产生(正面或负面)影响。我正在寻找此类情况的具体、具体示例。


13
基于个人观点(无论是故意挑衅还是无心之失),看法差异很大。我喜欢在某些情况下使用管道,但个人认为为了做一切事情都使用管道有时会显得愚蠢,例如:https://dev59.com/voXca4cB1Zd3GeqPHWHN。 - Ben Bolker
3
如果某个东西不符合你的编程风格并且对你没有用处,那么你应该避免使用它。如果你喜欢管道操作,那就继续使用吧。 - Roland
1
如果你真的想问这个问题,你可能可以重写它,关注于你问题的后半部分,例如:“在使用magrittr进行基于管道的编程时,是否存在潜在的错误或冲突,类似于使用NSE时可能出现的情况?我正在寻找具体的、具体的例子,而不是关于使用管道的有用性或智慧的一般意见。” - Ben Bolker
1
我编辑了问题,谢谢。它从来没有意图询问风格方面的意见。 - Johan Larsson
管道比什么都不做稍微慢一点,因为你在调用一个函数而不是...什么也不做。在大多数情况下,这个成本非常小,通常可以通过显著减少阅读代码所需的时间来抵消。最终,有很多优秀的 R 程序员使用它,也有很多优秀的 R 程序员不使用它。 - alistaire
3个回答

46

像R语言中所有高级函数一样,%>%需要较大的开销,因此不要在循环中使用它(包括隐式循环,例如*apply系列或dplyrdata.table等软件包中的每个组的循环)。以下是一个例子:

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 

直到看到这篇文章,我才意识到管道操作符有这么多的开销! - Haizi
5
从magrittr 2.0.0版本开始,管道操作符的性能显著提高。现在我分别得到了上述两个基准测试的时间为0.036秒和0.291秒。 - Johan Larsson
6
现在在 R 4.1.0 版本中添加的本地管道操作符 |> 没有额外开销。 - Mikko Marttila

22

往一个包中添加依赖项不应该轻率行事。一般来说,每个包所依赖的其他包都会增加未来维护风险,因为这些依赖项可能会更新,或者在依赖项停止维护的情况下。此外,这也会使得其他人在安装你的包时稍微麻烦一些——尤其是在网络连接不可靠的情况下,或者某些系统或硬件上安装某些包比较困难的情况下才会明显感觉到。但如果有人想将你的包放到一个闪存驱动器上以在其他地方安装,他们也需要确保拥有你的所有依赖项(及其依赖项...)。

Base R和默认包已经有很长的历史了,R-Core非常注重不引入会破坏下游依赖项的更改。 magrittr则要新得多,看起来它首次出现在CRAN上是在2014年2月。

实际上,magrittr已经稳定运行,并且看起来是一个低风险依赖项。特别是如果你只是导入%>% 并忽略它提供的更深奥的运算符(如dplyrtidyr等),那么你可能是相当安全的。它的流行几乎可以保证,即使它的创建者放弃了维护,也会有其他人接手维护。

现在在2022年,我们已经有了几个R版本,其中包含基础管道|>,因此只要你能运行R版本4.1.0或更高版本,就有了一个很好的没有依赖性的替代选择。


2
同样地,从教学角度来看,更多的依赖会导致平台特定的加载问题(Mac vs PC vs Linux,当前与旧操作系统,不同芯片等),关于警告和错误消息的问题等。我教授纯R,并查看我想使用的软件包的依赖关系。 - N Brouwer
这个答案是在tidyverse超级包发明之前的,可能是对为什么需要这个发明的一个相当简洁的解释。 - undefined

14

管道范式与“标准函数式编程”相比颠倒了函数应用的表现顺序。这是否会产生不良后果取决于函数符号学(我的原始拼写错误是打算写成“语义”,但是拼写检查器认为我是指符号学,这似乎也可以)。我认为管道创建的代码不太易读,但这是因为我已经训练自己从“内部到外部”来观察编码。比较一下:

 y <- func3 ( func2( func1( x) ) )

 y <- x %>% func1 %>% func2 %>% func3
依我的看法,第一个更易读,因为信息是向外(并且始终向左)“流动”的,并以最左侧位置的y结束,而第二个中的信息则向右“流动”,然后“掉头”向左发送。管道范式还允许无参函数应用,我认为这增加了出错的可能性。仅使用位置参数匹配的R编程常常会产生完全不可理解的错误消息,而自律地始终(或几乎总是)使用参数名称具有更多信息的错误消息的好处。
我的偏好是拥有一个一致方向的管道范式:
 y <- func3 %<% func2 %<% func1 %<% x
 # Or
 x %>% func1 %>% func2 %>% func3 -> y

我认为这实际上是 pkg-magrittr 的原始设计的一部分,其中我认为包括“左通道”和“右通道”。所以这可能是一个人因素设计问题。R具有从左到右的结合性,dplyr/magrittr 管道范例的典型用户通常遵循这个规则。我可能有僵硬的脑症候群,而你们所有年轻人可能都是未来,所以你们要做出选择。我真的很欣赏 Hadley 的目标,即使文件和 SQL 服务器被视为广义串行设备。

David Robinson 提供的示例表明跟踪参数是一个大问题,我完全同意。我的常规方法是使用制表符和空格来突出显示层次结构:

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)

毋庸置疑,使用语法感知的编辑器检查缺少逗号或括号会更容易,但在上述例子中未使用此类工具时,堆叠/间距方法确实突出了我认为存在的语法错误。(当我遇到困难时,我也会快速添加参数名称,但我认为这对于管道代码策略同样适用。)


3
有些人在管道符中使用 ->,这让我很疯狂!不过,我想每个人都有自己的喜好。 - Gregor Thomas
我知道 -> 在 R 中是可解析的。我只是想说,如果你使用右向数据流,那么右赋值似乎更加一致。 - IRTFM
"僵硬脑综合症":我理解你的感受。我用于编程的那部分大脑并不适合使用管道操作,因为这种方式对我来说并不能产生更易读的代码。虽然我可能可以重新训练它,但我并没有看到优势。幸运的是,R核心团队中的大多数人似乎也处于同样的情况。 - Roland
7
我认为所列示的示例在让每个函数只接收一个参数时有些刻意强求。以下哪个表达更清晰:func3 ( func2( func1(x, a) b, c) d, e, f) 或者 x %>% func1(a) %>% func2(b, c) %>% func3(d, e, f)?就哪个参数与哪个函数相对应而言,哪个更清晰? - David Robinson
5
除了@DavidRobinson所做的评论之外,管道编程在修改已编写的代码或重新排列函数顺序时更加容易处理。 - Johan Larsson
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接