1. R中的管道符

R 有magrittr包提供的管道符 %>%,也有最近原生提供的 |>

附: 已有的管道符的功能和差异

r语言中gsub r语言中%%是什么意思_字符串

本文向探究一下它们是怎么实现的。
本文只用R语言实现简单的管道符功能。复杂的以后再说。//todo

目标效果如下:

(1) R包magrittr提供的管道符 %>%

> library(magrittr)
> iris %>% dim %>% sum
[1] 155
> iris %>% dim() %>% sum()
[1] 155

> iris %>% dim() %>% sum(100,2000) #额外加2个参数
[1] 2255

(2) R 原生管道符 |>

> iris |> dim() |> sum(100,2000)
[1] 2255

(3) 查看源代码

> `%>%`
function (lhs, rhs) 
{
    lhs <- substitute(lhs)
    rhs <- substitute(rhs)
    kind <- 1L
    env <- parent.frame()
    lazy <- TRUE
    .External2(magrittr_pipe)
}
<bytecode: 0x55efd70e97b0>
<environment: namespace:magrittr>


> `|>`
Error: object '|>' not found

> `+` #能看到+也是一个函数,Primitive类型,可能是C写的
function (e1, e2)  .Primitive("+")

从交互界面看不到管道符的源码。
不过有一点需要注意:函数内第一步就是使用 substitute() 函数转换参数!

magrittr包的源码在github上,链接见末尾。Rcpp的代码还好,C语言的和R底层联系太深,目前还看不懂。//todo

2. 尝试

(1) version1: 不支持圆括号,那么就无法设置更多参数了

"%>>%" <- function(x, fun){
  if(is.function(x)) {
    function(...) fun(x(...))
  } else {
    fun(x)
  }
}

> iris %>>% dim %>>% sum
[1] 155


> iris %>>% dim() %>>% sum() #不能加圆括号,更不用说其他参数了
Error in dim() : 0 arguments passed to 'dim' which requires 1

另一个写法,相当于把多个函数合并,没法使用额外的参数;用着也挺别扭。

"%|>%" <- function(fun1, fun2){
    function(x){fun2(fun1(x))}
}
> fn001=dim %|>% sum  #合并函数
> fn001(iris)
[1] 155

3.我的实现

主要是2个R函数。
目前只实现了%>%的最简单功能。
测试环境: Ubuntu 20.04 + R 4.1.1

# helper: 把函数调用转为字符串,拆分出函数名并整合参数列表
parse_func=function(x2){
  if(!is.character(x2)){
    stop("must input a character!")
  }

  fname=-1
  arg.str=""
  if( endsWith(x2, ")") ){
    #找到第一个(
    start=grepRaw("\\(", x2);
    fname=substr(x2, 1, start-1)
    arg.str=substr(x2, start+1, nchar(x2)-1)
  }else{
    #没有() 时是不是函数?怎么判断
    tryCatch({
      if( is.function(eval(parse(text=x2)) ) ){
        fname=x2;
      }
    })
  }
  # string to list
  if(fname!="")
    arg.list=parse(text = paste0( "list(", arg.str, ")" ))
  else{
    fname=-1
    arg.list=""
  }
  #
  return(list(
    fname=fname,
    args=arg.list
  ))
}


# 主函数
"%>>2%" <- function(x, fun){
  # 1. 函数调用表达式 to 字符串
  x2=deparse(substitute(x));
  fun2=deparse(substitute(fun));
  # 2.提取函数名字和参数列表
  x3=parse_func(x2);
  fun3=parse_func(fun2);
  # 3.调用函数: 第2个的函数名( 第一个参数作为第一个参数,紧随第二个函数的其余参数)
  arg.list=c( list(eval(parse(text=x2))), eval(fun3$args) ) #如果参数中有表达式,这样写有问题,加上x就没问题了
  do.call(fun3$fname, arg.list)
};

# 主函数:这个是补充。todo: 合并到上面
"%>>3%" <- function(x, fun){
  # 1. 函数调用表达式 to 字符串
  x2=deparse(substitute(x));
  fun2=deparse(substitute(fun));
  # 2.提取函数名字和参数列表
  x3=parse_func(x2);
  fun3=parse_func(fun2);
  # 3.调用函数: 第2个的函数名( 第一个参数作为第一个参数,紧随第二个函数的其余参数)
  arg.list=c( list(eval(parse(text=x2))), eval(fun3$args, x) ) #如果参数中有表达式,这样写有问题,加上x就没问题了
  do.call(fun3$fname, arg.list)
};


# helper: 支持按列选择数据 http://adv-r.had.co.nz/Computing-on-the-language.html
select <- function(df, vars) {
  vars <- substitute(vars)
  var_pos <- setNames(as.list(seq_along(df)), names(df))
  pos <- eval(vars, var_pos)
  df[, pos, drop = FALSE]
}

4.测试

(1) 显示前n行

> iris %>>2% head(n=3)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa

> iris %>>2% head() %>>2% dim() %>>2% sum()
[1] 11

> iris %>>2% dim() %>>2% sum(100,2000)
[1] 2255

(2)支持设置参数: 修改列名

> iris %>% setNames( paste0("c",c(1,2,3,4, 5))) %>>2% head(n=3)
   c1  c2  c3  c4     c5
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa

(3)使用 ggplot2 绘图

> library(ggplot2)
> mtcars %>>2% head(n=30) %>%
  ggplot(aes(wt, mpg, col=factor(gear) )) + geom_point()

(4)按列名选择

> mtcars %>>2% head(n=3) %>>2% select(c("wt", "am", "mpg"))
                 wt am  mpg
Mazda RX4     2.620  1 21.0
Mazda RX4 Wag 2.875  1 21.0
Datsun 710    2.320  1 22.8

(5) 可以使用表达式参数 // todo

更多对比测试,输出结果相同:

> subset(iris, Petal.Length>6.5)
> iris %>% subset( Petal.Length>6.5)
> iris %>>3% subset( Petal.Length>6.5)
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
106          7.6         3.0          6.6         2.1 virginica
118          7.7         3.8          6.7         2.2 virginica
119          7.7         2.6          6.9         2.3 virginica
123          7.7         2.8          6.7         2.0 virginica

> iris %>>2% subset( Petal.Length>6.5)  #todo: 怎么把2个函数统一起来
Error in eval(fun3$args) : object 'Petal.Length' not found

(6) %>>3% 也有问题

> library(magrittr)
> iris %>>3% head() %>>3% subset(Sepal.Length>5)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
> iris %>>3% head() %>>3% subset(Sepal.Length>5) %>>3% dim()
[1] 2 5
> iris %>>3% head() %>>3% subset(Sepal.Length>5) %>>3% dim() %>>3% sum() #not work
Error in eval(fun3$args, x) : numeric 'envir' arg not of length one
> iris %>>3% head() %>>3% subset(Sepal.Length>5) %>>3% dim() %>>2% sum() #换成 %>>2%就可以了
[1] 7
> #
> iris %>>3% head() %>>3% subset(Sepal.Length>5) %>>3% dim() %>% sum()
[1] 7
> iris %>>3% head() %>>3% subset(Sepal.Length>5) %>>3% dim() |> sum()
[1] 7

todo

  • 已经测试到的 bug 怎么解决?
  • 不支持占位符等。