The functional environment in the lapply circuit
so I ran into this problem related to variables existing in different environments and it confused me very much as it doesn't fit my understanding of how functions look for different objects.
my toy example is very simple: I have a function foo
with one argument j
. foo
lives inside a loop function lapply
with an 'i' argument. now i
clearly exists in the environment lapply
(and is not included in the global). when called inside the lapply function foo
, it tries to find i
, as well as errors and errors:
foo <- function(j){
message('foo env: exists(j) ', exists('j'))
message('foo env: exists(i) ', exists('i'))
i
}
env.g <- environment()
invisible(lapply(1, FUN = function(i){
message('global env: exists(i) ', exists('i', envir = env.g))
message('lapply env: exists(i) ', exists('i'))
message(' ')
j <- i + 1
foo(j)
}
))
#global env: exists(i) FALSE
#lapply env: exists(i) TRUE
#foo env: exists(j) TRUE
#foo env: exists(i) FALSE
#Error in foo(j) : object 'i' not found
when, on the other hand, i
exists in the global environment, foo
okay with this:
i <- 10
foo()
#foo env: exists(j) TRUE
#foo env: exists(i) TRUE
#[1] 10
so my previous understanding was that if a function doesn't see a variable in its own environment, it moves on to the next one ( lapply
in my first example and a global env. in my second) until it finds It. however it does not explicitly go into the outer loop lapply
in the above ... why?
source to share
I believe this is because the function foo()
is being evaluated in the environment in which it is defined. In your example, it is foo()
defined in the global environment and is therefore i
not included in the scope. If you define foo()
inside an anonymous function, then it i
appears to be correctly evaluated.
env.g <- environment()
invisible(lapply(1, FUN = function(i){
message('global env: exists(i) ', exists('i', envir = env.g))
message('lapply env: exists(i) ', exists('i'))
message(' ')
j <- i + 1
foo <- function(j){
message('foo env: exists(j) ', exists('j'))
message('foo env: exists(i) ', exists('i'))
i
}
foo(j)
}
))
#global env: exists(i) FALSE
#lapply env: exists(i) TRUE
#foo env: exists(j) TRUE
#foo env: exists(i) TRUE
source to share
There are 4 types of environments associated with a function.
At startup:
rm(i) lapply(1, foo)
or even:
rm(i)
lapply(1, function(x) {
i <- 42
foo(x)
})
the situation is like this:
lapply
:
-
Enclosing env:
namespace:base
-
Env binding:
package:base
-
Env execution: created on the fly and enclosed in
.GlobalEnv
-
Calling env:
.GlobalEnv
and foo
:
-
Enclosing (where it was defined):
.GlobalEnv
-
Binding (where name
foo
is):.GlobalEnv
-
Execution (enclosed in Calling env): created on the fly and enclosed in an application ... I'm not even sure where, but going up the chain of environments there should be a runtime
lapply
-
Challenge: same, not really sure ... but that shouldn't matter
But:
(Possibly) contrary to intuition, the variables are not discovered by going to the dynamic scoping AKA "call stack" (this would be: exec env of foo
(then maybe some staging environments), then exec env lapply
(where we find i <- 42
), then .GlobalEnv
). but directly in the foo
AKA environment of lexical scope , then the chain of its closed environments, thereby bypassing the runtime lapply
and thus not finding it i
, even if it is explicitly declared just above ...
source to share