Understanding environments explains closures, packages, NSE and many confusing bugs.
An environment is a collection of name-value bindings plus a pointer to a parent environment. When R looks up a name, it searches the current environment, then its parent, and so on up to the global environment, the attached packages and finally the empty environment.
Each function call creates a fresh execution environment whose parent is the environment where the function was defined (not where it was called). That is lexical scoping, and it is what makes closures work: a function returned from another function keeps access to the variables of the call that created it.
environment(f)shows a function's enclosing environment;new.env()creates one;get,assignandexistswork with names as strings.<-always creates or modifies a binding in the current environment.<<-walks up the parents and modifies the first existing binding it finds (or creates a global). It is the tool for stateful closures, and a code smell elsewhere.
Unlike most R objects, environments are not copied on modification: they have reference semantics. That's why they're used for caches, mutable state and as the basis of R6 and Reference Classes.
Packages have a namespace environment, so a package's functions find their own helpers even if you define an object with the same name globally.
Example
make_counter <- function() {
count <- 0
function() {
count <<- count + 1 # modifies the enclosing binding
count
}
}
counter <- make_counter()
for (i in 1:2) counter()
print(counter()) #> [1] 3
print(get("count", environment(counter))) #> [1] 3
x <- "global"
f <- function() x
g <- function() { x <- "local to g"; f() }
print(g()) #> [1] "global" (lexical)
cache <- new.env()
remember <- function(env, key, val) assign(key, val, envir = env)
remember(cache, "a", 1)
print(ls(cache)) #> [1] "a" (no copy made)
print(environmentName(parent.env(globalenv())) != "") #> [1] TRUERemember
- Name lookup walks from the current environment through its parents
- A function's parent is where it was defined: lexical scoping
<<-modifies an existing binding in an enclosing environment- Environments have reference semantics
Watch out for
- Using
<<-inside ordinary functions to change globals, creating hidden dependencies.