y 不会“永远消失”,因为管道会调用您的函数,并且它也知道y。有一种方法可以恢复y,但它需要遍历调用堆栈。为了了解发生了什么,我们将使用?sys.frames 和?sys.calls:
“sys.calls”和“sys.frames”分别给出所有活动调用和帧的对列表,“sys.parents”返回每个帧的父帧索引的整数向量。
如果我们在您的 x_expression() 中添加这些,我们可以看到当我们从全局环境中调用 y %>% x_expression() 时会发生什么:
x_expression <- function(x) {
print( enquo(x) )
# <quosure>
# expr: ^.
# env: 0x55c03f142828 <---
str(sys.frames())
# Dotted pair list of 9
# $ :<environment: 0x55c03f151fa0>
# $ :<environment: 0x55c03f142010>
# ...
# $ :<environment: 0x55c03f142828> <---
# $ :<environment: 0x55c03f142940>
str(sys.calls())
# Dotted pair list of 9
# $ : language y %>% x_expression() <---
# $ : language withVisible(eval(...
# ...
# $ : language function_list[[k]...
# $ : language x_expression(.)
}
我用<--- 强调了重要部分。请注意,enquo 捕获的 quosure 位于函数的父环境中(堆栈底部的第二个),而知道 y 的管道调用一直位于堆栈顶部。
有几种方法可以遍历堆栈。 @MrFlick's answer 到一个类似的问题以及this GitHub issue 遍历来自sys.frames() 的框架/环境。在这里,我将展示一个替代方案,它遍历sys.calls() 并解析表达式以找到%>%。
第一个难题是定义一个将表达式转换为其Abstract Sytax Tree(AST)的函数:
# Recursively constructs Abstract Syntax Tree for a given expression
getAST <- function(ee) purrr::map_if(as.list(ee), is.call, getAST)
# Example: getAST( quote(a %>% b) )
# List of 3
# $ : symbol %>%
# $ : symbol a
# $ : symbol b
我们现在可以系统地将此函数应用于整个sys.calls() 堆栈。目标是识别第一个元素是%>% 的 AST;然后第二个元素将对应于管道的左侧(a %>% b 示例中的symbol a)。如果有多个这样的 AST,那么我们就处于嵌套的 %>% 管道场景中。在这种情况下,列表中的最后一个 AST 将是调用堆栈中的最低点,并且离我们的函数最近。
x_expression2 <- function(x) {
sc <- sys.calls()
ASTs <- purrr::map( as.list(sc), getAST ) %>%
purrr::keep( ~identical(.[[1]], quote(`%>%`)) ) # Match first element to %>%
if( length(ASTs) == 0 ) return( enexpr(x) ) # Not in a pipe
dplyr::last( ASTs )[[2]] # Second element is the left-hand side
}
(小注:我使用enexpr()而不是enquo()来确保函数在管道内外的一致行为。由于sys.calls()遍历返回一个表达式,而不是一个quosure,我们想做同样的事情在默认情况下也是如此。)
新函数非常健壮,可以在其他函数中使用,包括嵌套的%>% 管道:
x_expression2(y)
# y
y %>% x_expression2()
# y
f <- function() {x_expression2(v)}
f()
# v
g <- function() {u <- 1; u %>% x_expression2()}
g()
# u
y %>% (function(z) {w <- 1; w %>% x_expression2()}) # Note the nested pipes
# w