使用带有 %>% 的空括号的函数时有什么缺点,magrittr 的管道?

What are the disadvantages when using a function without empty parentheses with %>%, the pipe of magrittr?

提问人:GKi 提问时间:5/26/2023 最后编辑:Peter MortensenGKi 更新时间:5/27/2023 访问量:181

问:

我使用了管道,如其文档中所示,通过在此答案中为 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", .)

在管道中提供不带括号的函数时有哪些缺点,以及为其提供空括号的良好技术理由是什么?

R magrittr

评论

2赞 MrFlick 5/26/2023
您似乎所指的评论还指出,包作者不同意“建议”。这真的只是一个风格问题。就像 和 之间没有真正的区别,但人们“推荐”前者。编写函数以接受指向函数或函数调用的符号。<-->
0赞 GKi 5/26/2023
但是这个建议是有原因的吗?
4赞 Allan Cameron 5/26/2023
我认为这是一个品味、透明度和一致性的问题,而不是一个好的基于性能的理由。人们总是可以链接带有括号的调用,但并不总是可以编写没有括号的管道。当您浏览某人的代码时,能够看到他们正在链接对函数的调用是件好事。在交互式会话中,我通常不会使用括号,但如果在代码示例或包中使用,我会使用括号以保持一致性和清晰度。
0赞 MrFlick 5/26/2023
除了该评论之外,该建议在野外还有其他引用吗?一般来说,风格指南只是为了保持一致性而固执己见。缩进代码时,使用两个空格、四个空格或制表符之间没有有意义的技术差异(除非您非常关心 ascii 字符数)。这只不过是一个见仁见智的问题。
0赞 GKi 5/26/2023
作为风格指南,我会使用手册,在那里 () 我发现没有空括号的例子。?"%>%"

答:

9赞 Konrad Rudolph 5/26/2023 #1

但另一方面,也可以像函数一样使用,并且通常提供不带括号的函数。%>%

不,这是令人困惑的事情:没有单一的“通常提供”功能的方式,它完全取决于使用情况。

您可以使用 和 的示例。两者都是高阶函数,这意味着它们期望函数作为参数。1 由于他们期望函数作为参数,因此我们可以传递一个引用函数的名称。但是,除了名称之外,我们还可以传递一个任意表达式,该表达式的计算结果为函数。sapplydo.call

...事实上,不要纠结于你在示例中传递一个名字的事实,这是一个红鲱鱼。下面是一个示例,我们传递表达式的结果(返回一个函数):

make_adder = function (y) {
    function (x) x + y
}

sapply(1 : 3, make_adder(2))

但这可能会分散注意力,因为不希望函数对象作为其第二个参数。相反,它需要一个函数调用表达式%>%

在我上面的例子中,是一个常规函数,它使用标准计算来评估其参数。计算其参数 和 ,并将结果作为参数传递给。阿拉伯数字sapply1 : 3make_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)
}

这适用于任何有效的表达式:、 等。 将它们分别转换为 、 、 等,并计算得到的表达式。rhssum()head(3)%>%sum(lhs)sum(lhs, 3)

到目前为止,这是完全一致的。但是,作者选择允许额外的、完全不同的用法:除了传递函数调用表达式 as 之外,您还可以传递一个简单的名称。在这种情况下,做一些完全不同的事情。它不是构造一个注入 的新调用表达式并对其进行评估,而是直接调用:%>%rhs%>%lhsrhs(lhs)

`%>%` = function (lhs, rhs) {
    rhs_expr = substitute(rhs)

    if (is.name(rhs_expr)) {
        rhs(lhs)
    } else {
        # (code from above.)
    }
}

换句话说,接受两种根本不同类型的参数作为 ,并为它们做不同的事情。%>%rhs

这本身还不是问题。如果我们函数工厂作为 .这是一个高阶函数,它本身返回一个函数。 从上面看就是这样一个功能工厂。rhsmake_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

%>%插值成为 .lhsnew_rhsmake_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 为我们提供了大量控制权来控制我们想要如何计算表达式。通常,这非常方便。但是,如果不小心使用,可能会导致代码混乱和细微的错误,建议仔细权衡违反引用透明度的行为和好处。

评论

2赞 Konrad Rudolph 5/26/2023
为了给这个已经太长的答案添加一个历史轶事,“magrittr”并不是 R 中管道运算符的第一个实现,据我所知,第一个实现也有类似的不一致。