Applying R to Lifestyle and Brain Health Research
University of Kansas Medical Center
September 16, 2026
An environment is similar to a named list with four important exceptions:
We can use rlang::env() to create an environment.
An environment’s job is to bind a set of names to their values. The names in the environment are in no particular order.
Environments are modified in place and do not create a copy. Unlike other R objects, environments can also contain themselves (e.g., e1$d <- e1)
Printing an environment displays its memory address, but env_print can provide more useful information. We can also use names to retrieve a character vector of the names in the environment.
Two of the most fundamental environments are the current and global environment. The current environment is where your code is executing and the global environment is your workspace where interactive computation outside of a function takes place.
Every environment has a parent environment. This is used to implement lexical scoping, allowing R to look for values in the parent environment if they are not found in the current one.
You can set a parent environment by providing an unnamed argument to the env() function and identify the parent of an environment with env_parent.
The only environment that does not have a parent is the empty environment. The ancestors of every environment eventually terminate with the empty environment.
By default, env_parents will stop at the global environment as the ancestors of the global environment include all attached packages.
rlang::env_parents(e2b, last = rlang::empty_env())
#> [[1]] <env: 0x113640f70>
#> [[2]] $ <env: global>
#> [[3]] $ <env: tools:rstudio>
#> [[4]] $ <env: tools:positron>
#> [[5]] $ <env: package:stats>
#> [[6]] $ <env: package:graphics>
#> [[7]] $ <env: package:grDevices>
#> [[8]] $ <env: package:utils>
#> [[9]] $ <env: package:datasets>
#> [[10]] $ <env: package:methods>
#> [[11]] $ <env: Autoloads>
#> [[12]] $ <env: package:base>
#> [[13]] $ <env: empty>Regular assignment (<-) creates a variable in the current environment. Super assignment (<<-) modifies an existing variable found in a parent environment.
If <<- does not find an existing variable in the parent environments, it will create one in the global environment.
You can use the subsetting operators $ and [[ to get and set elements of an environment. However, you can not pass numeric indices to [[ as the names are unordered.
A value of NULL is returned if the binding does not exisit, but env_get can be used to return an error or a default value.
You can also add bindings to an environment with env_poke and env_bind.
env_poke takes a name and a valueenv_bind allows you to bind multiple valuesYou can determine whether a binding exisits with env_has and unbind a name from an environment with env_unbind.
The env_bind_lazy function creates delayed bindings to be evaluated the first time they are accessed. It’s primary use is in autoload to allow R packages to provide datasets that behave like they are loaded in memory (e.g., dplyr::starwars).
The env_bind_active function creates active bindings to be recomputed each time they are accessed. These bindings are used to implement the active fields of R6 functions.
Operating on every ancestor of an environment is possible with recursive functions. For example, writing a function like where can be used to find the environment of a given name using R’s scoping rules.
where <- function(name, env = rlang::caller_env()) {
if (identical(env, rlang::empty_env())) {
stop("Can't find ", name, call. = FALSE)
} else if (rlang::env_has(env, name)) {
env
} else {
where(name, rlang::env_parent(env))
}
}
# The base case
where("yyy")
#> Error: Can't find yyy
# The successful case
x <- 5
where("x")
#> <environment: R_GlobalEnv>
# The recursive case
where("mean")
#> <environment: base>The same where function can be rewritten with a while loop. We don’t often use recursion, so it may be easier to understand through iteration.
where <- function(name, env = rlang::caller_env()) {
while (!identical(env, rlang::empty_env())) {
if (rlang::env_has(env, name)) {
return(env)
} else {
env <- rlang::env_parent(env)
}
}
}
e4a <- rlang::env(rlang::empty_env(), a = 1, b = 2)
e4b <- rlang::env(e4a, x = 10, a = 11)
# Finds a in environment e4b
identical(where("a", e4b), e4b)
#> [1] TRUE
# Finds b in parent environment e4a
identical(where("b", e4b), e4a)
#> [1] TRUEEach package becomes a parent of the global environment when attached with library or require. The last package attached is the first parent of the global environment.
The search path allows you to see the order in which every package was attached. You can use base::search or rlang::search_envs to see the list of packages.
The last two environments on the search path are always the same:
When you attach another package, the parent of the global environment changes.
A function binds the current environment when it is created (called a function environment). Across other programming languages, functions that capture (or enclose) their environments are called closures.
You can get the function environment with base::environment or rlang::fn_env.
The function f() binds the environment that binds the name f to the function.
However, this isn’t always the case. Some times a name is bound to a new environment but the function binds the global environment.
The difference between binding and being bound is important as it changes how we find the name of the function vs how it finds its variables.
The goal of namespaces is to ensure that packages find their functions and that packages work the same regardless of what other packages are attached. R avoids this problem by associating every function in a package with a package and namespace environment.
::).Every binding in the package environment is also found in the namespace environment to ensure that all functions can use every other function in the package. But some bindings only occur in the namespace environment (i.e., internal or non-exported objects.), allowing the ability to hide internal implementation details.
Every namespace has an imports environment that contains bindings to all the functions used by the package.
A new environment is created to host execution every time a function is called. This is the execution environment and its parent is the function environment.
Take this function as an example:
The execution environment is garbage collected once the function completes.
There are a few ways to make the function environment stay around. First, you can explicitly return the environment.
Second, you can return an object with a binding to that environment. The following example uses a function factory (i.e., a function that makes functions) to illustrate that idea.
The enclosing environment of plus_one is the execution environment of plus in this function factory.
When plus_one is run, its execution environment captures the execution environment of plus.
The caller environment can be accessed with rlang::caller_env or base::parent.frame and provides the environment from which the function was called. It can be different based on how the function is called.
Executing a function creates two types of context:
The most common way to see a call stack in R is by looking at the traceback after an error has occurred.
We can also use lobstr::cst to print out the call stack tree. It prints the call stack from the beginning rather than the end like traceback.
A call stack often happens on a single branch when all arguments are eagerly evaluated. A more complex call stack can be created by including lazy evaluation.
The tree gets 2 branches because x is lazily evaluated. First, a() calls b() and then b() calls c(). The second branch starts when c() evaluates its argument x. It gets a new branch as it is evaluated in the global environment and not the environment of c().
Each element of the call stack is a frame, an internal data structure with three key components:
traceback prints.Environments are useful data structures as they have reference semantics. There are three common problems that this can help solve:
R for Lifestyle and Brain Health (R-LAB)