获取在“magrittr”管道调用的函数中计算为点的表达式

Posted

技术标签:

【中文标题】获取在“magrittr”管道调用的函数中计算为点的表达式【英文标题】:Get expression that evaluated to dot in function called by `magrittr` pipe 【发布时间】:2019-02-03 13:45:03 【问题描述】:

我有一个函数 x_expression(),它打印传递给参数 x 的表达式。

pacman::p_load(magrittr, rlang)

x_expression <- function(x) 
  print(enquo(x))


y <- 1

x_expression(y)
#> <quosure>
#>   expr: ^y
#>   env:  global

y %>% x_expression()
#> <quosure>
#>   expr: ^.
#>   env:  0x7ff27c36a610

所以你可以看到它知道y 已传递给它,但是当y%&gt;% 一起输入时,该函数返回打印.。有没有办法恢复 y 在它被管道输入的情况下,或者它永远消失了?简而言之,我想要的是像 x_expression() 这样的函数,但在上述两种情况下都会打印 y

这个问题确实与Get name of dataframe passed through pipe in R 类似,但更笼统一些。这个人只想要数据框的名称,我想要表达式,不管它是什么。但是,相同的答案可能适用于两者。我不喜欢这个几乎重复的问题的答案,该答案的作者也不喜欢。

【问题讨论】:

相关:***.com/questions/49074569/… 我已经看到了,但我对这个问题的理解还不足以将其应用于我的问题。 可能重复:***.com/questions/42560389/… Get name of dataframe passed through pipe in R的可能重复 这是不可能的。 【参考方案1】:

y 不会“永远消失”,因为管道会调用您的函数,并且它也知道y。有一种方法可以恢复y,但它需要遍历调用堆栈。为了了解发生了什么,我们将使用?sys.frames?sys.calls

“sys.calls”和“sys.frames”分别给出所有活动调用和帧的对列表,“sys.parents”返回每个帧的父帧索引的整数向量。

如果我们在您的 x_expression() 中添加这些,我们可以看到当我们从全局环境中调用 y %&gt;% 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(.)

我用&lt;--- 强调了重要部分。请注意,enquo 捕获的 quosure 位于函数的父环境中(堆栈底部的第二个),而知道 y 的管道调用一直位于堆栈顶部。

有几种方法可以遍历堆栈。 @MrFlick's answer 到一个类似的问题以及this GitHub issue 遍历来自sys.frames() 的框架/环境。在这里,我将展示一个替代方案,它遍历sys.calls() 并解析表达式以找到%&gt;%

第一个难题是定义一个将表达式转换为其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() 堆栈。目标是识别第一个元素是%&gt;% 的 AST;然后第二个元素将对应于管道的左侧(a %&gt;% b 示例中的symbol a)。如果有多个这样的 AST,那么我们就处于嵌套的 %&gt;% 管道场景中。在这种情况下,列表中的最后一个 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,我们想做同样的事情在默认情况下也是如此。)

新函数非常健壮,可以在其他函数中使用,包括嵌套的%&gt;% 管道:

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

【讨论】:

以上是关于获取在“magrittr”管道调用的函数中计算为点的表达式的主要内容,如果未能解决你的问题,请参考以下文章

R:在自编写的包中使用magrittr管道运算符

如何使用具有多参数功能的 magrittr 管道?

如何将管道链(magrittr)的结果提供给对象

%.% (dplyr) 和 %>% (magrittr) 之间的差异

magrittr 中的 %>% 和 %,% 有啥区别?

在不输入第一个参数的情况下使用管道