stas g
stas g

Reputation: 1513

function environment within lapply loop

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

Answers (2)

Aur&#232;le
Aur&#232;le

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

Oliver Burren
Oliver Burren

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

Related Questions