Show special primitive functions in the call stack

This question raised the following question: is there a way to view the special primitive that are on the call stack?

For example, create a function that returns a call stack on exit:

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

Calling this function and assigning its result to an object using assign allows you to avoid using special primitive functions:

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

But using the assignment operator, this is not counted on the stack

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

Of course, it may not be so difficult to see the assignment operator in the call stack, but other functions, such as rep and log , are also hidden

+6
source share
2 answers

I do not think that there is access to calls to primitive functions through the call stack. That's why.

When evaluating the "typical" function R:

  • The arguments presented are matched against formal arguments.
  • A new environment is created (with a pointer to its environment) and formal arguments are assigned to it.
  • The function body is evaluated in a newly created environment.

The environment chain that is created when function calls are nested within each other is a “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 do not appear on the call stack, because during evaluation they do not create an environment from the R side . No environment is created, so the environment does not appear on the call stack.

For a deeper understanding, here is how John Chambers describes the evaluation of primitive functions on page 464 Data Analysis Software :

The evaluation of a call to one of these functions begins in the usual way, but when the evaluator discovers that the function object is primitive and not the function defined in R, it forks into a completely different calculation. An object is only represented as a functional object with formal arguments and a call to the .Primitive () function with argument string. In fact, it essentially only contains the index in the table, which is part of the C code that implements the R core. Entering the table identifies C in the kernel, which is responsible for evaluating the calls of this particular primitive. The evaluator will take control of this procedure and await the routine for returning the C language pointer to the R object representing the call value.

+6
source

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

Well, it would be right if <- were on the call stack in your example. But this is not so.

A short summary: the usual evaluation of the function R treats the arguments as promises, which are evaluated lazily with ease. This means in the following call:

 foo(bar(baz)) 

bar(baz) is evaluated inside foo (if at all). Therefore, if we check the call stack inside bar , like this:

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

... then it looks like this:

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

Alas, as you noted, <- (and = ) is not a normal function, its primitive ( BUILTINSXP ). In fact, defined in source R , it looks like this:

 {"<-", 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 numbers mean. Here's the relevant part, explaining the leftmost digit:

Z = 1 talks about evaluating the arguments before the call ( BUILTINSXP )

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

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

This is why <- does not appear in sys.calls() : this is not the current call. It is called after the completion of bar .


Theres a way around this limitation: you can override <- / = in R code. If you do this, it will behave 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, be careful that this entails an unbearable performance hit for each subsequent assignment within the area where the <- redefined: in fact, it makes the assignment about 1000 (!!!) slower. This is usually not acceptable.

+1
source

All Articles