提问人:GKi 提问时间:5/26/2023 最后编辑:Peter MortensenGKi 更新时间:5/27/2023 访问量:181
使用带有 %>% 的空括号的函数时有什么缺点,magrittr 的管道?
What are the disadvantages when using a function without empty parentheses with %>%, the pipe of magrittr?
问:
我使用了管道,如其文档中所示,通过在此答案中为 RHS 提供一个没有空括号的函数,并得到一条评论,即推荐的约定是向 RHS 提供空括号。 %>%
magrittr
library(magrittr)
1:3 %>% sum # The documentation calls this: Basic use
1:3 %>% sum() # It's also possible to supply empty parentheses
1:3 |> sum() # And It's similar to |> the base pipe
一个优点可能是语法类似于 ,基本管道。|>
但另一方面,也可以像函数一样使用,并且通常提供不带括号的函数。%>%
`%>%`(1:3, sum)
sapply(list(1:3), sum)
`%=>%` <- sapply
list(1:3) %=>% sum
do.call(sum, list(1:3))
`%<%` <- do.call
sum %<% list(1:3)
在这种情况下,看起来不带括号就使用它是恒定的。
另一方面,使用占位符时,需要提供括号。
"axc" %>% sub("x", "b", .)
在管道中提供不带括号的函数时有哪些缺点,以及为其提供空括号的良好技术理由是什么?
答:
但另一方面,也可以像函数一样使用,并且通常提供不带括号的函数。
%>%
不,这是令人困惑的事情:没有单一的“通常提供”功能的方式,它完全取决于使用情况。
您可以使用 和 的示例。两者都是高阶函数,这意味着它们期望函数作为参数。1 由于他们期望函数作为参数,因此我们可以传递一个引用函数的名称。但是,除了名称之外,我们还可以传递一个任意表达式,该表达式的计算结果为函数。sapply
do.call
...事实上,不要纠结于你在示例中传递一个名字的事实,这是一个红鲱鱼。下面是一个示例,我们传递表达式的结果(返回一个函数):
make_adder = function (y) {
function (x) x + y
}
sapply(1 : 3, make_adder(2))
但这可能会分散注意力,因为不希望函数对象作为其第二个参数。相反,它需要一个函数调用表达式。%>%
在我上面的例子中,是一个常规函数,它使用标准计算来评估其参数。计算其参数 和 ,并将结果作为参数传递给。阿拉伯数字sapply
1 : 3
make_adder(2)
sapply
%>%
不是一个正则函数:它抑制了对第二个参数的标准计算。相反,它将表达式保留在未计算的形式中并对其进行操作。它这样做的方式相当复杂,但在最简单的情况下,它会将其第一个参数注入表达式中,然后对其进行计算。下面是一些伪代码来说明这一点:
`%>%` = function (lhs, rhs) {
# Get the unevaluated expression passed as `rhs`
rhs_expr = substitute(rhs)
new_rhs_expr = insert_first_argument_into(rhs_expr, lhs)
eval.parent(new_rhs_expr)
}
这适用于任何有效的表达式:、 等。 将它们分别转换为 、 、 等,并计算得到的表达式。rhs
sum()
head(3)
%>%
sum(lhs)
sum(lhs, 3)
到目前为止,这是完全一致的。但是,作者选择允许额外的、完全不同的用法:除了传递函数调用表达式 as 之外,您还可以传递一个简单的名称。在这种情况下,做一些完全不同的事情。它不是构造一个注入 的新调用表达式并对其进行评估,而是直接调用:%>%
rhs
%>%
lhs
rhs(lhs)
`%>%` = function (lhs, rhs) {
rhs_expr = substitute(rhs)
if (is.name(rhs_expr)) {
rhs(lhs)
} else {
# (code from above.)
}
}
换句话说,接受两种根本不同类型的参数作为 ,并为它们做不同的事情。%>%
rhs
这本身还不是问题。如果我们将函数工厂作为 .这是一个高阶函数,它本身返回一个函数。 从上面看就是这样一个功能工厂。rhs
make_adder
那么:怎么办?...1 : 3 %>% make_adder(2)
Error in make_adder(., 2) : unused argument (2)
哦,对了! 是一个函数调用表达式,因此第一个定义适用:转换表达式并计算它。因此,它尝试评估 ,但失败了,因为只期望一个参数。make_adder(2)
%>%
make_adder(2, 1 : 3)
make_adder
幸运的是,为了我们的理智,我们可以与 .这甚至不需要额外的规则或文档。稍加思考,它直接来自上面的第一个定义:我们需要添加另一层函数调用,因为我们要调用 返回的函数。以下作品:make_adder
%>%
%>%
make_adder
1 : 3 %>% make_adder(2)()
# 3 4 5
%>%
插值成为 .lhs
new_rhs
make_adder(2)(1 : 3)
我们可以通过将 的返回值赋给名称来使其更具可读性:make_adder(2)
add_2 = make_adder(2)
1 : 3 %>% make_adder(2)() # (1)
# \___________/
# v
# /‾‾‾\
1 : 3 %>% add_2() # (2)
我们在这里直接用一个新引入的名称替换了一个子表达式。这是一个非常基本的计算机科学概念,但它是如此强大,以至于它有自己的名字:引用透明度。这个概念使程序的推理变得更容易,因为我们知道我们总是可以为一个名称分配任意的子表达式,并在一段代码中使用该名称来代替它:(1)和(2)是相同的。
但是,实际上,引用透明度要求我们也可以反向进行替换,即将名称替换为它所引用的值。果然,这奏效了,我们恢复了原来的表达:
1 : 3 %>% add_2() # (1)
# \___/
# v
# /‾‾‾‾‾‾‾‾‾‾‾\
1 : 3 %>% make_adder(2)() # (2)
(1) 和 (2) 仍然相同。
但不幸的是,它并不总是有效:
1 : 3 %>% add_2 # (1)
# \___/
# v
# /‾‾‾‾‾‾‾‾‾‾‾\
1 : 3 %>% make_adder(2) # (2)
(1)有效,但(2)失败,即使我们只是用它的定义来代替。 不保留参照透明度。3add_2
%>%
这就是为什么在RHS上不使用括号是不一致的,也是为什么人们普遍不鼓励这样做(例如,tidyverse风格指南)。这也是(据我所知)为什么 R 核心开发人员决定始终需要函数调用表达式作为其 RHS,并且您不能省略括号。|>
1 我们对这个概念有一个特殊的词,因为接受函数作为参数在主流编程语言中曾经非常罕见。
2 这是一种简化。事实更复杂,但在这里无关紧要。如果出于好奇,请参阅 R 语言定义:参数评估。
3 在 R 中违反引用透明度非常容易,因为 R 为我们提供了大量控制权来控制我们想要如何计算表达式。通常,这非常方便。但是,如果不小心使用,可能会导致代码混乱和细微的错误,建议仔细权衡违反引用透明度的行为和好处。
评论
<-
->
?"%>%"