deparse(substitute())通常返回函数名,但在for循环内调用时返回函数代码

17

在一个非常具体的情况下,R语言的行为让我有些惊讶。假设我定义了一个函数square,它返回其参数的平方,就像这样:

square <- function(x) { return(x^2) }

我希望在另一个函数内调用此函数,并且在这样做时还要显示其名称。 我可以使用deparse(substitute())来实现。 然而,请考虑以下示例:

ds1 <- function(x) {
  print(deparse(substitute(x)))
}

ds1(square)
# [1] "square"

这是预期的输出,所以一切都很好。然而,如果我把函数包装在一个列表中,并使用for循环进行处理,将会发生以下情况:

ds2 <- function(x) {
  for (y in x) {
    print(deparse(substitute(y)))
  }
}

ds2(c(square))
# [1] "function (x) "   "{"               "    return(x^2)" "}"  

有人能向我解释为什么会发生这种情况,以及我如何防止它发生吗?

1个回答

19

一旦你在函数内使用x,它就会被评估,所以它“不再是一个(未评估的)表达式”,而是“其结果值(已评估的表达式)”。为了防止这种情况发生,您必须在第一次使用之前通过substitute捕获x

substitute的结果是一个可以像列表一样查询的对象。因此,您可以使用

x <- substitute(x)

然后是x[[1]](函数名)和x[[2]]及其后续参数(函数的参数)。

因此,这个可以工作:

ds2 <- function(x) {
    x <- substitute(x)
    # you can do `x[[1]]` but you can't use the expression object x in a
    # for loop. So you have to turn it into a list first
    for (y in as.list(x)[-1]) {
        print(deparse(y))
    }
}
ds2(c(square,sum))
## [1] "square"
## [1] "sum"

2
太棒了,谢谢。显然,这个技术术语是“promise对象”。虽然x是一个promise,但它的元素(当分配给y时)不再是promise,因此substitute返回一个不同的值。 - A. Stam
与我的目的有些奇怪(捕获变量名并在tidyverse中进行一些计算后将其作为标题),deparse是不必要的。substitute(deparse(var))和只使用substitute(var)有什么区别?此外,奖励问题:在新的tidyverse NSE中,这仍然是最佳实践吗? - JoeTheShmoe
@JoeTheShmoe substitute() 返回一个调用对象,而 deparse() 将调用对象转换为字符串。如果您将 substitute() 的结果提供给生成标题的函数,则可能会直接调用 deparse(),但我不太确定。在 tidyverse 中,您很少需要使用 substitute(),请参阅 Programming with dplyr 稿件,了解用户提供的列标题等所有反应方式。 - akraf
嗯,有趣。是的,我阅读了那篇小品文,这就是为什么我来到这里的原因。我找不到一个直接讨论将输入处理为字符的地方。我认为旧的小品文可能使用了一些奇怪的enquo()和sym()混合方式,但我现在没有看到它了,但也许我只是在里面错过了它。 - JoeTheShmoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接