Reputation: 1513
so i came across this problem that is to do with variables existing in different environments and it left me very confused as it does not fit with my understanding of how functions look for various objects.
my toy example is very simple: i have a function foo
taking one argument j
. foo
lives inside a function of an lapply
loop with an argument 'i'. now, i
clearly exists within the lapply
environment (and does not in the global one). when called within the lapply function foo
struggles to find i
and throws and error:
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
is okay with that:
i <- 10
foo()
#foo env: exists(j) TRUE
#foo env: exists(i) TRUE
#[1] 10
so my prior understanding was that if a function doesn't see a variable in its own environment it goes to the next one up (lapply
in my first example and global env. in my second one), until it finds it. however, it clearly doesn't go to the outer loop of lapply
in the above... why?
Upvotes: 4
Views: 1388
Reputation: 12819
There are 4 types of environments associated with a function.
When you run:
rm(i)
lapply(1, foo)
or even:
rm(i)
lapply(1, function(x) {
i <- 42
foo(x)
})
the situation is:
lapply
:
Enclosing env: namespace:base
Binding env: package:base
Execution env: created on the fly, and enclosed in .GlobalEnv
Calling env: .GlobalEnv
and foo
:
Enclosing (where it was defined): .GlobalEnv
Binding (where the name foo
is): .GlobalEnv
Execution (enclosed in Calling env): created on the fly, and enclosed... I'm not even sure where, but when going up the chain of enclosed environments, there should be the execution environment of lapply
Calling: same, not really sure... but it shouldn't matter
But:
(Possibly) contrary to intuition, variables are not found by going up the "call stack" AKA dynamic scoping (that would be: exec env of foo
, (then possibly some intermediary environments), then exec env of lapply
(where we would find i <- 42
), then .GlobalEnv
), but directly in the enclosing environment of foo
AKA lexical scoping, then the chain of its enclosed environments, thus bypassing the execution environment of lapply
, and thus not finding i
, even if it is explicitly declared just above...
Upvotes: 1
Reputation: 56
I believe it is because function foo()
is evaluated in the environment in which it is defined. In your example foo()
is defined in global environment and therefore i
is not in scope. If you define foo()
within the anonymous function then i
appears to be evaluated correctly.
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
Upvotes: 4