renvironmentscopinglexical-scope

Understanding ls() scoping and environments


I'm having trouble understanding why this piece of R code works the way it does. Here is a simple function:

weird_ls <- function() {
  
  some_env <- new.env()
  assign("a", value = 1, envir = some_env)
  assign("b", value = 2, envir = some_env)
  
  out <- function() {
    ls(envir = some_env)
  }
  
  return(out)
}

And here is what happens when I call it:

> f <- weird_ls()
> f()

[1] "a" "b"

But why is it the case? In my understanding, the object f is a function defined in the global environment. When I call it, it runs in its own (new) runtime environment where it executes ls(envir = some_env). But there is no some_env object in the runtime environment or in its parent, the global environment. In fact, some_env was only defined during the assignment f <- weird_ls() but was never returned by the function.

Can anyone help shed some light? I suspect I might be missing something on the rules of scoping and environments.


Solution

  • The environment, including all its contents, in which a function was defined is part of the function by so passing out the function we are also passing out that environment. The objects in the frame that was created while the function is run are only garbage collected if nothing points to them. Simplifying the example

    weird_ls <- function() {
    
      print(environment())
      a <- 1
      function() {}
    
    }
    
    f <- weird_ls() # environment/frame of runtime instance of weird_ls from print
    ## <environment: 0x00000265e7a84238>
    
    environment(f) # R still knows about that environment since it is part of f
    ## <environment: 0x00000265e7a84238>
    
    ls(environment(f))  # the objects in that environment are still there
    ## [1] "a"
    
    environment(f)$a  # we can retrieve the value of a
    ## [1] 1