官方

R now provides a simple native forward pipe syntax |>. The simple form of the forward pipe inserts the left-hand side as the first argument in the right-hand side call. The pipe implementation as a syntax transformation was motivated by suggestions from Jim Hester and Lionel Henry.

R-bloggers

R 4.1.0 introduces a pipe operator |> into the base R syntax

# R 4.1.0
rnorm(100, mean = 4, sd = 1) |>
    density() |>
    plot()

The new operator is nicer to type, and should be both more efficient and easier to debug, than the {magrittr} pipe.

如果考虑到向下兼容问题,或许在用到{magrittr}依赖的包的开发中,目前来说还是不太适合完全使用 |> 进行替代?

barnett874 速度一样是不可能的,这辈子都不会一样的。|> 是从语法解析器层面实现的,把 x |> f() 直接解析为 f(x),也就是 x |> f() 的速度几乎与直接运行 f(x) 一样;而 %>% 则没这么直接,它还要做一箩筐其它的事情。

chuxinyuan 我的理解是 %>% 的实现太复杂了,规则太多;而 |> 的实现干净利落,有且仅有一条规则,就是我上面说的,比如它要求管道右边必须是一个函数调用,而不能是裸函数,而 %>% 则二者皆可。

1:10 %>% head
1:10 %>% head()

1:10 |> head  # 这不可以
1:10 |> head()

|> 也不支持 %>% 发明的占位符 .。要用占位符的话,只能显式地写一个函数,不过可以是简写版的匿名函数,如 \(.) head(., 8),也就是一条反斜杠代替了 function 关键字。不过说实话我到现在仍然不习惯这个反斜杠,因为它在我脑子里根深蒂固成了转义符的代表,视觉上我很难把 \(x)function(x) 等价起来。

最后,不管是哪种管道,我个人对它们都并不是很热衷。%>% 的排错(debug)之麻烦,让我觉得简直难以忍受;若真能一气呵成写出一条管道,那皆大欢喜,而万一要是写不对,要查出是哪里错了,则麻烦得要死,所以我宁愿用结硬寨、打呆仗的办法写代码,哪怕这种呆办法被管道党一次次嘲弄。

x = 1:10
x = head(x)

因为 |> 的实现原理简单,所以出错后排错要容易得多,可以使用常见的排错手段,如 debug() 函数。

    倒不意外,油管哪个视频好像是什么会议上就有人说了马上管道就来了,还演示了示例代码但是确实没想到这么快。鼓掌👏

    ✓ [main*]> empfreq %>% 
         tibble(no = row.names(.))
    # A tibble: 12 x 2
       .       no   
       <table> <chr>
     1 0.235   0    
     2 0.275   1    
     3 0.230   2    
     4 0.105   3    
     5 0.050   4    
     6 0.045   5    
     7 0.015   6    
     8 0.020   7    
     9 0.005   8    
    10 0.005   9    
    11 0.010   10   
    12 0.005   11   
    ✓ [main*]> empfreq |> 
         tibble(no = row.names(.))
    Error: Internal error in `df_slice()`: Columns must match the data frame size.
    Run `rlang::last_error()` to see where the error occurred.

    试了一下 |>, 目前来说还是会使用 magrittr 包, 主要有三点:

    1. 我经常使用 %<>%, 它可以让代码更简洁, 让写代码的过程更流畅. 比如 x$y$z %<>% c(a) 这样的代码, 要是不用 %<>%, 写出来会一长串, 尤其是变量名比较长的时候.

    2. 如果值不是传给右边函数的第一个变量, |> 用起来会比较笨拙. 比如 x$y %>% {ifelse(is.null(.), ., 1)} 这样的代码, 要使用 |> 必须配合 lambda 函数的写法, 行文上就更不流畅.

    3. 另外, R 4.1.0 提供了 => 所谓的 pipe bind operator 来解决值传递给非首位变量的问题, 但是这让 R 的 pipe 设计在整体上非常不统一, 非常不奥卡姆剃刀.

    除非对速度有很高的要求, 不然代码流畅性和代码对人的可读性一定是我首先考虑的. 所以从以上几个角度来说, 我更倾向使用 magrittr.

    yihui
    看到个支持占位符的做法

    Sys.setenv("_R_USE_PIPEBIND_" = "true")
    • ryo 回复了此帖
    • ryo 觉得很赞

      yihui yihui ,你好
      看到这个更新,确实觉得不错。
      但在官网也没说明。新的管道操作有没有快捷键啊。是不是和{magrittr}一样的?
      我认为管道操作一定要有快捷键,这样写起代码来才顺畅

        x <- c(1:10, NA)
        
        a_fun <- function(x) {
          . <- x
          . <- ifelse(is.na(.), 0, .)
          . <- (. + 1) * 3
          . <- sort(.)
          head(., 5)
        }
        
        b_fun <- function(x) {
          x |>
            {\(.) ifelse(is.na(.), 0, .)}() |>
            {\(.) (. + 1) * 3}() |>
            sort() |> 
            head(5)
        }
        
        a_fun(x)
        #> [1]  3  6  9 12 15
        b_fun(x)
        #> [1]  3  6  9 12 15
        
        dput(a_fun)
        #> function (x) 
        #> {
        #>     . <- x
        #>     . <- ifelse(is.na(.), 0, .)
        #>     . <- (. + 1) * 3
        #>     . <- sort(.)
        #>     head(., 5)
        #> }
        dput(b_fun)
        #> function (x) 
        #> {
        #>     head(sort({
        #>         function(.) (. + 1) * 3
        #>     }({
        #>         function(.) ifelse(is.na(.), 0, .)
        #>     }(x))), 5)
        #> }

        <sup>Created on 2021-05-24 by the reprex package (v2.0.0)</sup>

        想了好久,感觉我还是更喜欢传统的写法。也可以在Rstudio设置 ". <- " 快捷键。写出来的代码也可以很简练。如函数a_fun。这本身就符合奥卡姆剃刀原则。这是其一。
        其二, 使用管道符号以后,写出来函数的格式不稳定。比如,dput(b_fun) 以后和原来的代码完全不一样。虽然这对大部分用户来说并不是什么问题,但我本人不太喜欢。

        个人观点。

          JackieMe 你不是第一个这么说的人。一堆特殊字符(大括号、反斜杠、小括号、点、竖线、小于号)堆在一起,确实是太 Perl 了。

          所以我还是喜欢 a_fun 里的写法,一步一赋值,简单明了,排错方便。管道的写法太怂恿用户追求形式上的美感,而这种外表美掩盖了潜在的代价。每次看到这种讨论,总让我联想起古诗与现代诗。形式美是一种美,但特别容易病态和狂热,舍本逐末(比如我认为诗的平仄差不多就行,完全不必成为规则;管道也不必管天管地,不是所有代码都适合管道;管道本是 Unix 的发明,也没见到 Shell 脚本到处都是管道吧,通常也都是普通的 if 判断或 for 循环;我一直都不是很理解为啥 R 社区这么狂热于管道)。

          a_fun 这种一步一赋值的写法最容易被人诟病的是,当变量名比较长时,写起来就很啰嗦了。这里用 . 简写看起来还行。我通常绕过这个问题的办法是写新函数:如果需要重复操作一个变量,那么就把这部分代码抽出去写一个新函数,新函数的参数用短变量名。如:

          c_fun <- function(x) {
            x_squared <- x^2
            x <- (x_squared + 1) * (x_squared -2)
            head(x, 5)
          }

          为了避免重复敲 x_squared 这个长名字,可以新写一个函数,参数用短名:

          d_fun <- function(x) {
            (x + 1) * (x -2)
          }

          然后调用新函数:

          c_fun <- function(x) {
            x <- d_fun(x^2)
            head(x, 5)
          }

          抽离局部代码到新函数这种做法也便于单元测试。不过我这都是从开发者角度说的。数据分析的代码风格会不一样,可能不会频繁抽离新函数。

          我自己刚开始接触管道的时候, 有过什么都想上管道的倾向 ... 现在处于一种混杂的状态. 至于什么时候用什么时候不用, 我的标准是认知上的流畅和简洁. 但这个 "简洁" 并不是完全说输入的代码越少越好. 我下面举一些例子:

          my_data[, c("var_1", "var_2", "var_3")] %>% my_fun()

          上面这个例子我觉得就是一个简单的, 但是显示管道优点的例子. 因为它的形式是贴合你的思维过程的. 如果你用传统的方式来写, 就会有点拗口:

          my_fun(my_data[, c("var_1", "var_2", "var_3")])

          因为 my_fun() 这个操作是后发生的.

          如果用中间的变量, 我个人会觉得有点笨拙:

          new_data <- my_data[, c("var_1", "var_2", "var_3")]
          my_fun(new_data)

          这么写对我个人来说还有三个不爽的点:

          1. new_data 这个变量是不重复使用的, 有点废话.
          2. new_data 会阻断思维过程, 本来一句话能说清楚的, 变得冗长了. 我下次看的时候, 加工时间会更长.
          3. 如果你采用中间变量是为了更清晰, 更显性, 那么这背后需要你花心思去取直观简洁的变量名, 这个过程对我来说很痛苦.

          但就算我支持管道, 我也不会刻意这样写:

          c("var_1", "var_2", "var_3") %>%
            {my_data[, .]} %>%
            my_fun()

          因为我思考的时候, my_data[, c("var_1", "var_2", "var_3")] 是整体出现的. 字面上的, 刻意的简洁反而是损害思维上的简洁的. 除非 c("var_1", "var_2", "var_3")] 是我之前处理的结果.

          这些例子看起来很无足轻重, 但是是我实际开发时的体验的缩影.

          开辟第三战场

          # Example - 1
          1:10 ->.; head(.) # 如果换行分号就可去掉了。
          
          # Example - 2
          1:10 ->.
            head(.)
          
          # Example - 3
          library(dplyr)
          mtcars ->.
            group_by(., cyl) ->.
            summarise(., mpg_avg = mean(mpg)) -> df

            chuxinyuan
            可以设置个 " ->.; "的快捷键。 想换行就换行,不想换行就接着写。哈哈~~

            10:1 |> sort() |> head(5) ->.; ifelse(. < 5, 0, .) ->.; plot(.)

            这样是不是有点梦幻了。

            等同于这样:

                . <- head(sort(10:1), 5)
                . <- ifelse(. < 5, 0, .)
                plot(.)