How does an environment remember that it exists?

Viewed 70

Take

adder <- local({
x <- 0
function() {x <<- x+1; x}
})

or equivalently

adderGen <- function(){
x <- 0
function() {x <<- x+1; x}
}
adder<-adderGen()

Calling adder() will return 1 calling it again returns 2, and so on. But how does adder keep count of this? I can't see any variables hitting the global environment, so what is actually being used to store these? Particularly in the second case, you'd expect adder to forget that it was made inside of a function call.

2 Answers

Every function retains the environment in which it was defined as part of the function. If f is a function then environment(f) shows it. Normally the execution environment within adderGen would be discarded when it exited but because adderGen passes a function out whose environment is the execution environment within adderGen that environment is retained as part of the function that is passed out. We can verify that by displaying the execution environment within adderGen and then verify that it is the same as the environment of adder. The trace function will insert the print statement at the beginning of the body of adderGen and will show the execution environment each time adderGen runs. environment(adder) is the same environment.

trace(adderGen, quote(print(environment())))
## [1] "adderGen"

adder <- adderGen()
## Tracing adderGen() on entry 
## <environment: 0x0000000014e77780>

environment(adder)
## <environment: 0x0000000014e77780>   

To see what is happening, let us define the function as follows:

adderGen <- function(){
  print("Initialize")
  x <- 0
  function() {x <<- x+1; x}
}

When we evaluate it, we obtain:

adder <- adderGen()
# [1] "Initialize"

The object that has been assigned to adder is the inside function of adderGen (which is the output of adderGen). Note that, adder does not print "Initialize" any more.

adderGen
# function(){
#   print("Initialize")
#   x <- 0
#   a <- function() {x <<- x+1; x}
# }
adder
# function() {x <<- x+1; x}
# <environment: 0x55cd4ebd3390>

We can see that it also creates a new calling environment, which inherits the variable x in the environment of adderGen.

ls(environment(adder))
# [1] "x"
get("x",environment(adder))
# [1] 0

The first time adder is executed, it uses the inherited value of x, i.e. 0, to redefine x as a global variable (in its calling environment). And this global variable is the one that it is used in the next executions. Since x <-0 is not part of the function adder, when adder is executed, the variable x is not initialized to 0 and it increments by one the current value of x.

adder()
# [1] 1
Related