Note: This document describes internal implementation details. They are not required knowledge for users of the ‘box’ package and module authors.
To ensure that module environments are properly isolates from each
other, and to enable fine-grained control over imports and exports,
loaded modules (as well as packages loaded via
correspond to a mesh of several interconnected environments.
Unfortunately the exact relationship between these environments isn’t trivial, so the following document aims at explaining the gist.
Fundamentally, module environments follow a similar architecture to package environments, but they deviate in crucial ways. For a good explanation of package environments, see How R Searches and Finds Stuff by Suraj Gupta, and Environments in Advanced R by Hadley Wickham. The following assumes that the reader has a more than passing familiarity with these concepts.
When speaking of “package environments” above and in the following,
this refers to packages as handled by base R and the R package namespace
functionality. For packages that are loaded by
the rules are the same as for modules.
Let’s now consider an example of several loaded modules, and how they interact.
This example will use three modules — the minimum number necessary to show some of the interactions of the module environments. Let’s define these three modules. We’ll do so in inverse order of their usage:
b defines and exports the function
f, and in addition defines the function
#' @export box::use(./b[g = f, ...]) box::use(./c[...]) #' @export box::use(./c) #' @export f = function () 'a$f' f_of_c1 = c$f f_of_c2 = get('f', parent.env(environment())) stopifnot(identical(f_of_c1, f_of_c2))
a imports and re-exports all names from
b (changing the name of
g); it also imports, but does not re-export, all names from
c (and, additionally, defines an alias for the module,
which it exports).
It further defines and exports the function
defines, but does not export, the functions
f_of_c2 (which are defined such that they are identical to
each other — hopefully it will become clear why later).
Finally, let’s use the modules by executing the following code:
Now the name
a is defined in
refers to the module environment of module
a. The exported
a are also
.GlobalEnv (but not defined inside
The loaded modules can be represented by the following schematic, with each box corresponding to an environment:
Let’s go over the different types of environment associated with each module, and how they are connected.
Modules are loaded into their own dedicated environment, the
module namespace. Every name that is defined by a module is
defined inside it. It is thus also the enclosing environment of the
module’s functions. Lastly, the module namespace also stores
meta-information about the module in a hidden member named
This corresponds to the package namespace.
The parent environment of the module namespace is the imports
environment, which contains all the names that a module imports via
attachment declarations in
The parent environment of the imports environment is the R
base namespace environment. The module imports environment
thus corresponds to the package imports environment.
The module export environment, also called just “module environment”
in the code base, contains all names that are marked as exported by a
module. If users create a module alias in their
call, the alias will be a reference to this environment.
Similarly, attached names are selected as a subset from the module export environment (then copied into the imports environment).
There is no direct equivalent to this environment in R packages, because R packages do not distinguish between exported names and names imported by the code that loaded the package. By contrast, ‘box’ needs to distinguish the set of exports from the set of names that are imported (= “attached”) by client code.
Module import environments store imports from one or more module into
another. However, modules can also be imported from inside the global
environment, or from inside functions, which have their own local
execution environment (the stack frame). When this happens, a new
imports environment is created on the fly, and attached to the importing
parent.env chain. Calling
box::use with a non-empty attach list in
.GlobalEnv is therefore similar to calling
library. In particular, inside
box::use(pkg[...]) in pretty much equivalent to
library(pkg), and is discouraged for the same
library(pkg) is discouraged.