8

这个问题引发了以下问题:有没有办法查看调用堆栈中的特殊原始函数?

例如,创建一个在退出时返回调用堆栈的函数:

myFun <- function(obj){
  on.exit(print(sys.calls()))
  return(obj)
}

调用此函数并将其结果分配给对象 usingassign避免使用特殊的原始函数:

> assign("myObj",myFun(4))
[[1]]
assign("myObj", myFun(4))

[[2]]
myFun(4)

但是使用赋值运算符,这会被排除在堆栈之外

> `<-`(myObj, myFun(6))
[[1]]
myFun(6)

当然,希望在调用堆栈中看到赋值运算符可能并不常见,但其他函数(例如repandlog也被隐藏)

4

2 回答 2

9

我认为没有任何方法可以通过调用堆栈访问对原始函数的调用。这就是为什么。

当评估“典型” R 函数时:

  1. 提供的参数与形式参数匹配。
  2. 创建一个新环境(带有指向其封闭环境的指针),并将形式参数分配给它。
  3. 函数的主体在新创建的环境中进行评估。

当函数调用相互嵌套时建立的封闭环境链是“调用堆栈”或“帧堆栈” sys.calls()sys.frames()等等提供了一些访问。

我强烈怀疑对原始函数的调用不会出现在调用堆栈上,因为在评估期间没有创建 R 端环境。没有创建环境,因此调用堆栈上不会出现任何环境。

如需更深入的了解,John Chambers 在Software for Data Analysis的第 464 页上描述了对原始函数的评估:

对这些函数之一的调用的求值以通常的方式开始,但是当求值者发现函数对象是原始函数而不是 R 中定义的函数时,它会分支到完全不同的计算。该对象似乎只是一个带有形式参数的函数对象和一个带有字符串参数的函数 .Primitive() 调用。实际上,它本质上只包含一个表的索引,该表是实现 R 核心的 C 代码的一部分。表的条目标识了核心中的一个 C 例程,该例程负责评估对这个特定原语的调用。求值程序会将控制权转移到该例程,并期望该例程返回一个指向表示调用值的 R 对象的 C 语言指针。

于 2012-10-24T22:31:25.823 回答
3

我不认为乔希的回答是正确的。

好吧,如果在您的示例中位于调用堆栈上,那是正确的。但事实并非如此<-

小回顾:正常的 R 函数评估将参数视为在访问时延迟评估的承诺。这意味着在以下调用中:

foo(bar(baz))

bar(baz)在内部 进行评估foo(如果有的话)。因此,如果我们检查内部的调用堆栈bar,如下所示:

bar = function (x) {
    sys.calls()
}

…然后它看起来如下:

[[1]]
foo(bar(baz))

[[2]]
bar(baz)

唉,正如您所指出的,<-(and =) 不是一个普通函数,它是一个原始函数 ( BUILTINSXP)。事实上,它在 R 源码中是这样定义的

{"<-",      do_set,     1,  100,    -1, {PP_ASSIGN,  PREC_LEFT,   1}},

看一下第四个参数:100. 此代码之前的注释解释了数字的含义。这是相关部分,解释最左边的数字:

Z=1 表示在调用 ( BUILTINSXP)之前评估参数

这意味着在分配之前bar(baz)评估以下调用代码:

`<-`(x, bar(baz))

这就是为什么<-没有出现在列表中的原因sys.calls():它不是当前调用。bar它在完成评估后被调用。


有一种方法可以解决这个限制:您可以在 R 代码中重新定义<-/ 。=如果你这样做,它的行为就像一个普通的 R 函数:

`<-` = function (lhs, rhs) {
    name = as.name(deparse(substitute(lhs), backtick = true))
    rhs # evaluate expression before passing it to `bquote`, for a cleaner call stack
    eval.parent(bquote(base::`<-`(.(name), .(rhs))))
}

但是,请注意,这将对重新定义范围内的每个后续分配产生不可忽略的性能影响<-:事实上,它使分配大约慢了 1000 倍(!!!)。这通常是不可接受的。

于 2017-07-02T13:45:08.353 回答