9

I am trying to use get in series of function calls, but the lookup of the object names seems to skip environments. For example:

foo <- 1 # variable in .GlobalEnv

getter <- function(x) {get(x)}
getter("foo") # returns 1, which is expected

f1 <- function() {
  foo <- 2 # local variable in the function scope
  getter("foo")
}

f1() # still returns 1, would've expected to return 2

Why is it that calling f1 returns the foo in the global environment and not the foo in the calling function's environment?

How do I have get look in the calling function's environment? Setting pos = sys.parent() does not seem to work.

hgcrpd
  • 1,820
  • 3
  • 19
  • 32

2 Answers2

17

You are being tripped up by the subtle differences between frames and environments (which is even more subtle since frames are environments, or maybe environments are frames) and the difference between lexical and dynamic scoping. There are some details in the help page for parent.frame and other places spread across various documentation.

To try and simplify:

Your getter function has its own environment where variables local to that function are stored (x in this case). Since R is lexically scoped that means that the functions environment has a parent environment which is defined by where the function is defined, the global environment in this case (if it were defined inside of another function then the parent environment would be the env for that function).

When you call f1 and it calls getter then getter tries to find the variable foo, it first looks in its own environment, does not find it there, then looks in its parent environment which is the global env and finds foo with the value of 1.

Your thinking goes along the lines of dynamic scoping, which the frames approximate. When f1 is called it gets its own environment (within which foo will be assigned the value 2), then it calls the getter function. The environment of foo is not the parent of getter's env (lexical scoping), but the environment of f1 is the parent frame of getter since getter was called from f1, so to look in the environment of f1 you need to tell the get function to look in the parent frame rather than the parent environment.

The summary of this is that the parent environment is the environment where a function was defined (lexical scoping), the parent frame is the frame/environment from which the function was called (simulated dynamic scoping).

Community
  • 1
  • 1
Greg Snow
  • 48,497
  • 6
  • 83
  • 110
  • 2
    +1 Very nice explanation, thank you. This deserves to be the accepted answer, If you merge my code solution in, I shall remove my answer, so yours can get accepted. – Andrie Sep 20 '12 at 04:33
  • @Andrie I think both answers are good, yours solved the immediate problem and mine gave the background. I have no problem with both remaining and which is the accepted should be the decision of the original poster. I do appreciate your upvote. – Greg Snow Sep 20 '12 at 15:50
6

If you define getter to look in the parent frame, it works:

getter <- function(x) get(x, envir=parent.frame())

Then:

getter("foo")
[1] 1

f1()
[1] 2
Andrie
  • 176,377
  • 47
  • 447
  • 496
  • 1
    That did it, thanks! Why is it that parent.frame() works but not sys.parent()? Is there a document that discusses how all these different functions work, and the differences between referring to environments by number or by name? – hgcrpd Sep 19 '12 at 09:57
  • 2
    @hgcrpd `sys.parent` returns an integer, not the frame/environment. `parent.frame` uses the `sys.parent` information with `sys.frame` to get the frame (not just its integer value). – Greg Snow Sep 19 '12 at 17:12