编写自己的/自定义的管道运算符

GKi*_*GKi 10 r pipe

我想编写一个自定义管道运算符,其中使用的运算符名称为 open。它可能是例如%>%, %|%, :=, ... 也许需要根据所需的运算符优先级进行选择,如相同函数中所述,但使用名称 %>% 会导致与使用名称 := 时相比不同的结果

使用的占位符名称是开放的,但._很常见,需要显式放置(不会自动放置作为第一个参数)。

评估环境是开放的。但在这个答案中,看起来应该避免使用用户环境。

它应该能够在用户环境中保留该值,以防它与占位符同名。

1 %>% identity(.)
#[1] 1
.
#Error: object '.' not found

. <- 2
1 %>% identity(.)
#[1] 1
.
#[1] 2
Run Code Online (Sandbox Code Playgroud)

它应该能够更新用户环境中的值,包括占位符的名称。

1 %>% assign("x", .)
x
#[1] 1

"x" %>% assign(., 2)
x
#[1] 2

1 %>% assign(".", .)
.
#[1] 1

"." %>% assign(., 2)
.
#[1] 2

x <- 1 %>% {names(.) <- "foo"; .}
x
#foo 
#  1 
Run Code Online (Sandbox Code Playgroud)

它应该从左到右评估

1 %>% . + 2 %>% . * 3
#[1] 9
Run Code Online (Sandbox Code Playgroud)

我知道定义管道运算符的最短方法.是:在新环境中设置 lhs 的值并计算 rhs 的值:

`:=` <- function(lhs, rhs) eval(substitute(rhs), list(. = lhs))
Run Code Online (Sandbox Code Playgroud)

但这里无法创建或更改调用环境中的值。

因此,另一种尝试是将 lhs 分配给调用环境中的占位符.,并在调用环境中评估 rhs。

`:=` <- function(lhs, rhs) {
  assign(".", lhs, envir=parent.frame())
  eval.parent(substitute(rhs))
}
Run Code Online (Sandbox Code Playgroud)

在这里,大多数事情都可以工作,但它会创建或覆盖变量。在调用范围内。

因此添加以删除退出时的占位符:

`:=` <- function(lhs, rhs) {
  on.exit(if(exists(".", parent.frame())) rm(., envir = parent.frame()))
  assign(".", lhs, envir=parent.frame())
  eval.parent(substitute(rhs))
}
Run Code Online (Sandbox Code Playgroud)

现在的问题只是.将从调用环境中删除(如果它已经存在)。

因此,检查是否.已经存在,将其存储并在退出时重新插入,以防 lhs 未被修改。

`:=` <- function(lhs, rhs) {
  e <- exists(".", parent.frame(), inherits = FALSE)
  . <- get0(".", envir = parent.frame(), inherits = FALSE)
  assign(".", lhs, envir=parent.frame())
  on.exit(if(identical(lhs, get0(".", envir = parent.frame(), inherits = FALSE))) {
            if(e) {
              assign(".", ., envir=parent.frame())
            } else {
              if(exists(".", parent.frame())) rm(., envir = parent.frame())
            }
          })
  eval(substitute(rhs), parent.frame())
}
Run Code Online (Sandbox Code Playgroud)

但尝试时失败了:

. <- 0
1 := assign(".", .)
.
#[1] 0
Run Code Online (Sandbox Code Playgroud)

以下给出了预期的结果,但我不确定它是否真的从左到右进行评估。

1 := . + 2 := . * 3
#[1] 9
Run Code Online (Sandbox Code Playgroud)

Moo*_*per 5

这意味着您需要算术运算的优先级

1 %>% . + 2 %>% . * 3
Run Code Online (Sandbox Code Playgroud)

这会驳回任何%>%操作,:=是一个不错的选择,我们也可以使用?,让我们一起使用:=

assign()通常<-默认情况下会做同样的事情。但你的例子暗示不然:

您想assign(".", "foo")覆盖旧点,但names(.) <- "foo"(并且可能. <- "foo")覆盖新点而不影响旧点。

我相信实现这一点的唯一方法是特殊情况assign(),我在下面做了,你的测试是满意的。

通过此解决方案,我们评估调用者的子环境中的表达式,该表达式继承自该子环境中除点之外的所有值,以及在未提供环境参数时在调用者中分配的修改后的分配函数。

1 %>% . + 2 %>% . * 3
Run Code Online (Sandbox Code Playgroud)

创建于 2023-05-03,使用reprex v2.0.2