我想编写一个自定义管道运算符,其中使用的运算符名称为 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)
这意味着您需要算术运算的优先级
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