[1] 1 2 3
[1] 1 2 3
Chapter 8: Symbols and Environments
Shih Chien University
2026-08-03
We have danced around environments since Chapter 3; time to define them. An environment is an R object containing:
Every evaluation context has an environment. To resolve a symbol, R searches the current environment first, then recursively searches parent environments until it finds a match.
Defining a variable assigns a symbol to a value in an environment. When an object is built from symbols, R resolves them at construction time:
You can postpone symbol resolution by quoting the expression and evaluating it later — each eval resolves symbols as of now:
A promise delays evaluation until the variable is first used:
[1] 5 2 3
Packages use promises to expose objects without loading them into memory. Two caveats: you cannot test whether an object is a promise, nor recover the environment where one was created.
Environments are objects (internally, hash tables of symbol mappings — an efficiency trick exploited in Chapter 24). The vocabulary:
| Function | Description |
|---|---|
assign / get / exists |
bind, fetch, or test a name in an environment |
objects |
all names defined in an environment |
remove (rm) |
drop objects from an environment |
search |
names of attached packages — the chain of parent environments |
searchpaths |
paths of attached packages |
attach / detach |
add/remove a list, data frame, or data file to/from the search path |
emptyenv |
the empty environment — every chain ends here |
parent.env |
the parent of an environment |
baseenv |
the environment of the base package |
globalenv / .GlobalEnv |
the user’s workspace |
environment |
a function’s environment (no argument: the current one) |
new.env |
a fresh environment object |
The two everyday calls — what’s here, and clean it up:
Each session gets a fresh environment for your objects: the global environment. Surprisingly, it is not the root of the tree — it is a leaf: search() lists it first, and walking the parents from .GlobalEnv climbs through the attached packages toward the root:
[1] "package:stats"
[1] "package:graphics"
[1] "package:grDevices"
[1] "package:utils"
[1] "package:datasets"
[1] "package:methods"
[1] "Autoloads"
[1] "base"
[1] "R_EmptyEnv"
Attached packages line up as parents, ending at base and finally the empty environment — the one environment with no parent, to which all chains lead.
Calling a function creates a new environment; the arguments are bound to symbols there. (In language lingo: R is lexically scoped.)
[1] "a" "b" "c" "d"
Only the arguments appear — everything else lives in parent environments. And note carefully: a function’s parent environment is where it was created, not where it is called. For functions you define at the console the two coincide; for functions from packages they differ.
R also maintains a stack of calling environments (a stack: add and remove only at the top — push and pop, like cafeteria trays). Each call pushes an environment; each return pops one.
| Function | Description |
|---|---|
sys.call |
the current function call, as a language object |
sys.frame |
the calling environment |
sys.nframe |
number of the current frame (0 at the console) |
sys.function |
the function being evaluated |
sys.parent |
number of the parent frame |
sys.calls / sys.frames / sys.parents |
calls, environments, parents for the whole stack |
sys.on.exit |
the on.exit expression of the current frame |
sys.status |
sys.calls + sys.parents + sys.frames in one list |
parent.frame |
sys.frame(sys.parent(n)) — the parent frame |
Why care? A package function sometimes needs a symbol’s meaning in the calling context. Modeling functions do exactly this: a formula contains symbol names but no data, and when you omit the data argument, the function hunts for the variables in the calling environment.
eval evaluates an expression in an arbitrary environment:
The book’s example: a timing wrapper that records the start time, evaluates its argument in the caller’s frame, and prints the elapsed time:
Time an inefficient function that grows a vector element by element (console transcript):
The timing is nice; the point is the last line — the assignment happened in the calling environment, so returned.vector exists there afterward. (Aside: pre-allocating with length(return.vector) <- n before the loop cuts the time from ~1.5 s to ~0.04 s — a preview of Chapter 24.)
Three convenient wrappers around eval:
evalq(expr, ...) ≡ eval(quote(expr), ...) — quote for me;eval.parent(expr, n) ≡ eval(expr, parent.frame(n)) — evaluate in the caller;local(expr) ≡ eval(quote(expr), envir=new.env()) — evaluate in a fresh environment.eval.parent shortens the timing function nicely:
Treating a data frame or list as an environment lets you use its element names like symbols:
Error:
! 找不到物件 'a'
[1] 6
$a
[1] 1
$b
[1] 2
$c
[1] 3
$d
[1] 6
with(data, expr) evaluates and returns the result; within(data, expr) modifies and returns the data.
attach adds the objects of a “database” — a list, data frame, or saved data file — to the search path; detach removes them:
pos is the search-path position, name labels the attached database (used again by detach), warn.conflicts warns about masked names, and unload controls namespace unloading.
Warning
Use attach sparingly. With several data frames sharing column names, it becomes genuinely hard to tell which object a name refers to. Prefer transform to change columns and with to evaluate expressions against a data frame.
Invalid expressions produce errors; questionable ones produce warnings:
Error in `12 / "hat"`:
! 二元運算子中有非數值引數
Error in `if (c(TRUE, FALSE)) ...`:
! 條件的長度 > 1
(The second was merely a warning in the book’s era; modern R promotes it to an error.) Why discuss exceptions in the environments chapter? Because when an exception occurs, the interpreter may abandon the current function and signal the exception in the calling environment — exception handling and environments are intertwined.
Three levels of telling the user something, from fatal to informative:
Error in `doWork()`:
! Could not open the file: file that doesn't exist
Warning in doNoWork("another file that doesn't exist"): File does not exist:
another file that doesn't exist
This function does nothing.
In your own programs: stop on errors that invalidate the work; warning for problems the caller should know about; message for chatter.
Suppose foo calls bar, bar sometimes fails (can’t open a file, say), and foo wants to survive and try something else. The simple tool is try, which hides most of the machinery:
[1] 1
[1] "Error in UseMethod(\"open\") : \n 沒有適用的方法可將 'open' 套用到 \"character\" 類別的物件\n"
attr(,"class")
[1] "try-error"
attr(,"condition")
<simpleError in UseMethod("open"): 沒有適用的方法可將 'open' 套用到 "character" 類別的物件>
[1] "try-error"
try(expr, silent) evaluates expr; on failure it returns an object of class "try-error" (and prints the message unless silent=TRUE).
The capable tool is tryCatch, taking an expression, a set of handlers, and a final expression:
Evaluation order: R evaluates expression; if a condition (error or warning) arises, R picks the handler whose argument matches the condition’s class; afterward finally is evaluated — with the handlers no longer active. A taste:
Copyright. These slides are adapted from R in a Nutshell: A Desktop Quick Reference (2nd ed.) by Joseph Adler, O’Reilly Media. All rights reserved by the original author and publisher.
Non-commercial use only. These materials are strictly for educational purposes and may not be used for commercial gain.
Attribution. Any reproduction, distribution, or use of these materials must properly credit the original source.
R in a Nutshell: A Desktop Quick Reference