Show special primitive functions in call stack

放肆的年华 提交于 2019-11-30 19:59:49

I don't think there's any way to access calls to primitive functions via the call stack. Here is why.

When a "typical" R function is evaluated:

  1. The supplied arguments are matched to the formal arguments.
  2. A new environment (with a pointer to its enclosing environment) is created, and the formal arguments are assigned into it.
  3. The body of the function is evaluated in the newly created environment.

The chain of enclosing environments that is built up when function calls are nested within one another is the "call stack" or "frame stack" to which sys.calls(), sys.frames() and the like provide some access.

My strong suspicion is that calls to primitive functions don't appear on the call stack because no R-side environment is created during their evaluation. No environment is created, so no environment appears on the call stack.

For some more insight, here's how John Chambers describes the evaluation of primitive functions on page 464 of Software for Data Analysis:

Evaluation of a call to one of these functions starts off in the usual way, but when the evaluator discovers that the function object is a primitive rather than a function defined in R, it branches to an entirely different computation. The object only appears to be a function object with formal arguments and a call to the function .Primitive() with a string argument. In reality, it essentially contains only an index into a table that is part of the C code implementing the core of R. The entry of the table identifies a C routine in the core that is responsible for evaluating calls to this specific primitive. The evaluator will transfer control to that routine, and expects the routine to return a C-language pointer to the R object representing the value of the call.

I don’t think Josh’s answer is correct.

Well, it would be correct if <- were on the call stack in your example. But it isn’t.

Small recap: normal R function evaluation treats arguments as promises that are evaluated lazily when accessed. This means that in the following call:

foo(bar(baz))

bar(baz) is evaluated inside foo (if at all). Consequently, if we inspect the call stack inside bar, like so:

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

… then it looks as follows:

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

[[2]]
bar(baz)

Alas, as you noted, <- (and =) isn’t a normal function, it’s a primitive (BUILTINSXP). In fact, it’s defined in the R source as follows:

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

Take a look at the fourth argument: 100. The comment before this code explains what the digits mean. Here’s the relevant part, explaining the leftmost digit:

Z=1 says evaluate arguments before calling (BUILTINSXP)

This means that the following code the call to bar(baz) is evaluated before the assignment:

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

That’s why <- doesn’t appear in the list of sys.calls(): it isn’t a current call. It gets called after bar finishes evaluating.


There’s a way to work around this limitation: you can redefine <-/= in R code. If you do this, it behaves like a normal R function:

`<-` = 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))))
}

However, beware that this will incur a non-negligible performance hit for every subsequent assignment within the scope where <- is redefined: in fact, it makes assignment roughly a factor 1000 (!!!) slower. This is not usually acceptable.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!