Skip to content
Environments
step 1/5

Reading — step 1 of 5

Learn

~1 min readEnvironments and Scoping

In R, everything has an environment. Functions, the global namespace, even names you bind. Understanding environments is key to closures, packages, and metaprogramming.

Create and inspect:

e <- new.env()
assign("x", 5, envir = e)
get("x", envir = e)        # 5
ls(envir = e)               # "x"
rm("x", envir = e)

Or use $:

e$x <- 5
e$x                          # 5

Function environments — every function captures its enclosing environment:

make_counter <- function() {
    count <- 0
    function() {
        count <<- count + 1     # <<- modifies enclosing scope
        count
    }
}

c1 <- make_counter()
c1()    # 1
c1()    # 2

environment(c1)             # the env where count lives

The <<- operator searches outward from the function's enclosing environment for the name. If found, modifies it. If not, creates in global. Use cautiously.

Environment lookup chainglobalenv(), baseenv(), package envs:

find("mean")                # "package:base"
environment(mean)            # base namespace

When R looks up a name, it walks: current function env → enclosing → ... → global → loaded packages → base.

Closures share environments:

make_account <- function(balance) {
    deposit  <- function(n) balance <<- balance + n
    withdraw <- function(n) balance <<- balance - n
    get_balance <- function() balance
    list(deposit = deposit, withdraw = withdraw, balance = get_balance)
}

a <- make_account(100)
a$deposit(50)
a$withdraw(20)
a$balance()                  # 130

All three closures share the balance upvalue.

local({...}) for clean private scope:

computed <- local({
    x <- 5
    y <- 10
    x + y                    # last expression
})
# computed = 15
# x and y are NOT in the global scope

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…