Bizarro管道->.;是否有不推荐使用的缺点?
从 R-Version 4.1.0 开始,管道|>处于稳定版本。当将 lhs 传递给第一个参数以外的参数时,手册的示例显示:
mtcars |> subset(cyl == 4) |> (function(d) lm(mpg ~ disp, data = d))()
或使用时 (x)
mtcars |> subset(cyl == 4) |> ((d) lm(mpg ~ disp, data = d))()
或者使用当前需要激活的PIPEBIND:
Sys.setenv(`_R_USE_PIPEBIND_` = TRUE)
mtcars |> subset(cyl == 4) |> . => lm(mpg ~ disp, data = .)
|>也可以使用Bizarro 管道 代替->.;
mtcars |> subset(cyl == 4) ->.; lm(mpg ~ disp, data = .)
由于 R 中管道符号的一个目的是允许以一种可能使处理步骤序列 更容易遵循的方式编写嵌套的调用序列,至少对我来说,这也由->.;. Bizarro 管道并不是真正的管道,但对我来说,它目前是一个受欢迎的替代方案,|>尤其是在将 lhs 传递到第一个参数以外的参数的情况下。但是在使用它时,我得到了不使用它的评论。
所以我想知道Bizarro管道是否有建议不要使用它的缺点?
到目前为止,我看到它.在环境中创建或覆盖并保留此引用,这将强制修改副本。但是当调用一个函数时,参数中有数据,也会创建对这个数据的引用。当使用for循环时,使用var后保持不变。
for(i in iris) {}
tracemem(i) == tracemem(iris[[ncol(iris)]])
#[1] TRUE
同样对于性能,它显示出的缺点并不多:
x <- 42
library(magrittr)
Sys.setenv(`_R_USE_PIPEBIND_` = TRUE)
#Nonsense operation to test Performance
bench::mark(x
, identity(x)
, "x |> identity()" = x |> identity()
, "x |> ((y) identity(y))()" = x |> ((y) identity(y))()
, "x |> . => identity(.)" = x |> . => identity(.)
, "x ->.; identity(.)" = {x ->.; identity(.)}
, x %>% identity
)
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr
# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int>
#1 x 60.07ns 69.03ns 13997474. 0B 0 10000
#2 identity(x) 486.96ns 541.91ns 1751206. 0B 175. 9999
#3 x |> identity() 481.03ns 528.06ns 1812935. 0B 0 10000
#4 x |> ((y) identity(y))() 982.08ns 1.08µs 854349. 0B 85.4 9999
#5 x |> . => identity(.) 484.06ns 528.06ns 1815336. 0B 0 10000
#6 x ->.; identity(.) 711.07ns 767.99ns 1238658. 0B 124. 9999
#7 x %>% identity 2.86µs 3.23µs 294945. 0B 59.0 9998
回答
奇异管道的主要问题是它会产生隐藏的副作用,并且更容易产生细微的错误。它降低了代码的可维护性。
.变量的持久存在使得以后很容易意外地引用这个值:如果你在某个时候忘记赋值并认为你做了,它的存在会掩盖错误。很容易排除这种可能性,但此类错误相当普遍,更糟糕的是,非常不明显:您不会收到错误消息,只会得到错误的结果。相比之下,如果您在某处忘记了管道符号,则会立即收到错误消息。
更糟糕的是,奇怪的管道以两种不同的方式隐藏了这种容易出错的副作用。首先,因为它使分配不明显。我认为以前是->分配不应该使用,因为左到右分配隐藏了一个副作用,并且副作用应该进行语法明显。这种情况下的副作用是赋值,它应该发生在它最突出的地方:在表达式的第一列中,而不是隐藏在它的末尾。这是对使用->(或任何其他试图掩盖副作用的尝试)的根本反对,不仅限于奇异的管道。
而且因为.默认情况下是隐藏的(从lsIDE 中的检查器窗格和从它的检查器窗格),这使得意外依赖它变得更加容易。
因此,如果您想分配一个临时名称而不是使用管道,只需这样做。但:
- 执行从右到左的赋值,即使用
name = valueorname <- value,notvalue -> name。 - 使用描述性名称。
我再怎么强调也不为过,这是微妙错误的实际来源——不要低估它!
另一个问题是它的使用破坏了编辑器对自动格式化代码的支持。在某些 IDE 中,这是通过插件“可解决的问题”,但实际上,该解决方案解决了一个本不应该存在的问题。为了澄清我的意思,如果您使用的是奇怪的管道,您大概需要一个悬挂的缩进,即沿着这些线的东西:
mtcars ->.
subset(cyl == 4) ->.
lm(mpg ~ disp, data = .)
……但是自动缩进不会像这样缩进代码,并且自动格式化程序会展平悬挂的缩进。
这些问题都不是禁止的(尽管第一个非常严重);但在不存在正参数的用于使用比扎罗管他们果断地打破平衡。毕竟,奇怪的管道解决了哪些问题不能通过适当的管道操作符1或常规赋值来更好地解决?如果您不能使用 R 4.1,请使用“magrittr”。如果您不喜欢 'magrittr' 的语义,请编写您自己的管道运算符,使用许多其他现有实现之一,或者仅使用常规赋值。
最后,有人可能会争辩说,这段代码非常不寻常,足以让读者感到困惑,但老实说,如果用法是一致的并且在某处清楚地记录在案,我认为这不是一个非常有说服力的论点。但它提出了另一个反对向初学者推荐使用它的论据。
1当然,这很容易回答:|>不允许显式点替换。虽然我理解反对支持它的论点,但它的缺失鼓励了诸如 bizarro pipe 之类的黑客行为这一事实是一个非常有力的论据,即这实际上是一个巨大的错误。