【问题标题】:Show special primitive functions in call stack在调用堆栈中显示特殊的原始函数
【发布时间】:2012-10-15 00:22:04
【问题描述】:

This question 提示以下问题:有没有办法查看调用堆栈中的special primitive 函数?

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

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

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

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

[[2]]
myFun(4)

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

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

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

【问题讨论】:

  • +1 提出了一个非常有趣且表述清晰的问题。 R Internals 指南中的链接部分也很有趣:我之前没有考虑过使用 Primitive 函数的各种动机。 &lt;-logUseMethod 都是原语,但原因完全不同。
  • @JoshO'Brien 我认为这些动机不一定是正确的——它们都可以通过.Internal 函数来完成

标签: r callstack


【解决方案1】:

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

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

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

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

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

如需更多了解,请参阅 John Chambers 在Software for Data Analysis 第 464 页上描述原始函数评估的方式:

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

【讨论】:

  • 另见src/main/eval.c顶部的cmets
  • @hadley -- 这很有趣,但我还是有点想不通。如果我知道那里讨论的BUILTIN 调用与"builtin""special" 原始函数之间的关系会有所帮助。卢克·蒂尔尼 (Luke Tierney) 是否说现在为 &lt;-log(例如)创建了上下文有时 ? Brian Ripley 的括号注释中的"foreign" 代码是什么?唔。还有很多东西要学。
  • @JoshO'Brien,感谢您的出色回答和指向 SoDA 的指针。它在工作的书架上,但我还没有读到最后一章。在阅读它并查看eval.c 文件之后,看起来,在c 级别,有一种机制可以确定一个对象是否被分配了一个名称(NAMED())。如果有可能获得指向由 Tyler 函数创建的对象的指针,则可以在 c 代码中查询该对象是否已分配。我也有很多东西要学!
【解决方案2】:

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

好吧,如果&lt;- 在您的示例中的调用堆栈上,它是正确的。 但不是

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

foo(bar(baz))

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

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

……那么它看起来如下:

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

[[2]]
bar(baz)

唉,正如你所说,&lt;-(和=)不是一个正常的函数,它是一个原始函数(BUILTINSXP)。其实就是defined in the R source如下:

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

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

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

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

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

这就是&lt;- 没有出现在sys.calls() 列表中的原因:它不是当前通话。它在bar 完成评估后被调用。


有一种方法可以解决这个限制:您可以在 R 代码中重新定义 &lt;-/=。如果你这样做,它的行为就像一个普通的 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))))
}

但是,请注意,在重新定义 &lt;- 的范围内,每个后续分配都会对性能造成不可忽视的影响:事实上,它使分配大约是 1000 倍(!! !) 慢点。这通常是不可接受的。

【讨论】:

    猜你喜欢
    • 2014-11-13
    • 1970-01-01
    • 2015-07-30
    • 2013-10-13
    • 1970-01-01
    • 2014-10-25
    • 2019-11-22
    • 2021-12-06
    • 1970-01-01
    相关资源
    最近更新 更多