7.5 Call stacks

There is one last environment we need to explain, the caller environment, accessed with rlang::caller_env(). This provides the environment from which the function was called, and hence varies based on how the function is called, not how the function was created. As we saw above this is a useful default whenever you write a function that takes an environment as an argument.

parent.frame() is equivalent to caller_env(); just note that it returns an environment, not a frame.

To fully understand the caller environment we need to discuss two related concepts: the call stack, which is made up of frames. Executing a function creates two types of context. You’ve learned about one already: the execution environment is a child of the function environment, which is determined by where the function was created. There’s another type of context created by where the function was called: this is called the call stack.

7.5.1 Simple call stacks

Let’s illustrate this with a simple sequence of calls: f() calls g() calls h().

f <- function(x) {
  g(x = 2)
}
g <- function(x) {
  h(x = 3)
}
h <- function(x) {
  stop()
}

The way you most commonly see a call stack in R is by looking at the traceback() after an error has occurred:

f(x = 1)
#> Error:
traceback()
#> 4: stop()
#> 3: h(x = 3) 
#> 2: g(x = 2)
#> 1: f(x = 1)

Instead of stop() + traceback() to understand the call stack, we’re going to use lobstr::cst() to print out the call stack tree:

h <- function(x) {
  lobstr::cst()
}
f(x = 1)
#> █
#> └─f(x = 1)
#>   └─g(x = 2)
#>     └─h(x = 3)
#>       └─lobstr::cst()

This shows us that cst() was called from h(), which was called from g(), which was called from f(). Note that the order is the opposite from traceback(). As the call stacks get more complicated, I think it’s easier to understand the sequence of calls if you start from the beginning, rather than the end (i.e. f() calls g(); rather than g() was called by f()).

7.5.2 Lazy evaluation

The call stack above is simple: while you get a hint that there’s some tree-like structure involved, everything happens on a single branch. This is typical of a call stack when all arguments are eagerly evaluated.

Let’s create a more complicated example that involves some lazy evaluation. We’ll create a sequence of functions, a(), b(), c(), that pass along an argument x.

a <- function(x) b(x)
b <- function(x) c(x)
c <- function(x) x

a(f())
#> █
#> ├─a(f())
#> │ └─b(x)
#> │   └─c(x)
#> └─f()
#>   └─g(x = 2)
#>     └─h(x = 3)
#>       └─lobstr::cst()

x is lazily evaluated so this tree gets two branches. In the first branch a() calls b(), then b() calls c(). The second branch starts when c() evaluates its argument x. This argument is evaluated in a new branch because the environment in which it is evaluated is the global environment, not the environment of c().

7.5.3 Frames

Each element of the call stack is a frame32, also known as an evaluation context. The frame is an extremely important internal data structure, and R code can only access a small part of the data structure because tampering with it will break R. A frame has three key components:

  • An expression (labelled with expr) giving the function call. This is what traceback() prints out.

  • An environment (labelled with env), which is typically the execution environment of a function. There are two main exceptions: the environment of the global frame is the global environment, and calling eval() also generates frames, where the environment can be anything.

  • A parent, the previous call in the call stack (shown by a grey arrow).

Figure 7.2 illustrates the stack for the call to f(x = 1) shown in Section 7.5.1.

The graphical depiction of a simple call stack

Figure 7.2: The graphical depiction of a simple call stack

(To focus on the calling environments, I have omitted the bindings in the global environment from f, g, and h to the respective function objects.)

The frame also holds exit handlers created with on.exit(), restarts and handlers for the condition system, and which context to return() to when a function completes. These are important internal details that are not accessible with R code.

7.5.4 Dynamic scope

Looking up variables in the calling stack rather than in the enclosing environment is called dynamic scoping. Few languages implement dynamic scoping (Emacs Lisp is a notable exception.) This is because dynamic scoping makes it much harder to reason about how a function operates: not only do you need to know how it was defined, you also need to know the context in which it was called. Dynamic scoping is primarily useful for developing functions that aid interactive data analysis, and one of the topics discussed in Chapter 20.

7.5.5 Exercises

  1. Write a function that lists all the variables defined in the environment in which it was called. It should return the same results as ls().

  1. NB: ?environment uses frame in a different sense: “Environments consist of a frame, or collection of named objects, and a pointer to an enclosing environment.” We avoid this sense of frame, which comes from S, because it’s very specific and not widely used in base R. For example, the frame in parent.frame() is an execution context, not a collection of named objects.↩︎