0

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.

weakCoder
  • 11
  • 3
  • 3
    *"`some_env` was never returned by the function"*. Yes it was, it was evaluated in the body of `weird_ls` and returned already evaluated, it became part of the body of `f`. – Rui Barradas Aug 18 '23 at 11:46
  • Thanks Rui, that's the piece I was missing. I thought the enclosing environment was discarded after calling `weird_ls()` but instead it is saved with `f` – weakCoder Aug 18 '23 at 12:29

1 Answers1

2

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
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • Thanks! It's clear now. Basically every time I save a function I am also saving the environment in which it was defined and which can be accessed by the function itself (with precedence over the calling environment). – weakCoder Aug 18 '23 at 12:25
  • 1
    More precisely, a reference to the environment is part of the function. If you defined two functions and returned them in a list, both would refer to the same environment, they wouldn't get separate copies. – user2554330 Aug 18 '23 at 14:34