How stable is the call stack, specifically -1?

Viewed 37

Say I have some function, f:

f <- function() {
  print(sys.calls())
}

Say I have two additional functions,

g <- function() {
  f()
}
h <- function() {
  f()
}

Calling these functions result in:

> g()
[[1]]
g()

[[2]]
f()

> h()
[[1]]
h()

[[2]]
f()

Assuming that

  1. None of the functions are further modified or overwritten,
  2. The only places where f is ever called is inside g or h.

How reliable/stable/confident can I be that f() will always appear immediately after g() in the call stack, such that I can inside f() check sys.call(-1)[[1]] to determine whether f was called by g or h?

To put it another way, is there some use-pattern I'm missing where a user would do something that would insert another call in the stack between the call to g or h, and the call to f?

1 Answers

The call stack is stable in the sense that directly calling these functions will always result in the correct outer function being printed.

However, this does not mean that you can use this method to directly identify the outer function whenever f() is invoked. The problem comes when g or h are passed as arguments in functional programming contexts. These are very commonly used in R, even by end users.

For example, a caller could use g inside do.call

do.call(g, list())
#> [[1]]
#> do.call(g, list())
#> 
#> [[2]]
#> (function() {
#>   f()
#> })()
#> 
#> [[3]]
#> f()

Note that there is not a stand-alone g() immediately before the call to f() because of the way that g has been invoked.

The same will be true if g or h are invoked inside one of the *apply family of functions, or Map, or ave or Reduce. And this is just in the base package, let alone extension packages.

Related