关于《R语言实战编程》中setup函数环境的技术咨询
setup() Closure from Hands-on Programming with R Great question! This setup() function is a perfect example of closures in R—a pattern that uses lexical scoping and function environments to maintain state between function calls. Let’s break down how the environment mechanics work here step by step.
1. What’s a Closure, and How Does setup() Qualify?
A closure is a function that retains access to variables from its enclosing (parent) environment, even after the parent function has finished executing. When you run cards <- setup(deck):
- R creates a new, temporary execution environment for the
setup()call (let’s call thissetup_env). - Inside
setup_env, three objects are created:DECK: A copy of the inputdeckdataset (the original, unshuffled card deck).DEAL: A nested function that handles dealing cards.SHUFFLE: A nested function that resets and shuffles the deck.
setup()returns a list containing references toDEALandSHUFFLE. Crucially, these nested functions "remember" their enclosing environment (setup_env), even aftersetup()finishes running.
2. Environment Hierarchy for DEAL() and SHUFFLE()
Every function in R has two key environments:
- Execution Environment: Created each time the function is called, to store variables specific to that call (like
cardinDEAL()orrandominSHUFFLE()). - Enclosing Environment: The environment where the function was defined. For
DEALandSHUFFLE, this issetup_env.
When DEAL() or SHUFFLE() tries to access a variable (like deck or DECK), R first checks their own execution environment, then falls back to their enclosing environment (setup_env), and so on up the environment chain.
3. How DEAL() Modifies the Deck State
Let’s look at the critical line in DEAL():
assign("deck", deck[-1,], envir = parent.env(environment()))
environment()returns the execution environment of the currentDEAL()call.parent.env(environment())gets the parent of that execution environment—which issetup_env, the enclosing environment whereDEALwas defined.assign()updates thedeckvariable insidesetup_envto be the original deck minus the first card. This means the next time you calldeal(), it will use the updated deck fromsetup_env.
The first line of DEAL()—card <- deck[1,]—works because R looks for deck in setup_env (since it’s not in DEAL()’s execution environment).
4. How SHUFFLE() Resets the Deck
SHUFFLE() uses similar environment logic to reset the deck:
assign("deck", DECK[random,], envir = parent.env(environment()))
- It uses
DECK(the original, unshuffled deck stored insetup_env) to create a randomly ordered version. - Again,
assign()writes this new shuffled deck back to thedeckvariable insetup_env, so subsequentdeal()calls will use the shuffled deck.
5. Why This Pattern Is Powerful: State Persistence
When you assign deal <- cards$deal and shuffle <- cards$shuffle, these variables point to the functions that reference setup_env. This means:
- Every call to
deal()modifies the samedeckvariable insetup_env—the state of the deck persists between calls. - You can create multiple independent deck instances by calling
setup()multiple times. For example:deck1 <- setup(original_deck) deck2 <- setup(original_deck)deck1$deal()anddeck2$deal()will modify separatedeckvariables in their own uniquesetup_envenvironments—no global variables needed!
Key Takeaways
- Closures let you encapsulate state (like the current deck) without polluting the global environment.
- The enclosing environment of a nested function is the execution environment of its parent function.
parent.env()andassign()let nested functions modify variables in their enclosing environment, maintaining state between calls.
内容的提问来源于stack exchange,提问作者kurtzdoni

