Set a functions environment to that of the calling environment (parent.frame) from within function
Well, a function cannot change it's default environment, but you can use eval
to run code in a different environment. I'm not sure this exactly qualifies as elegant, but this should work:
helpFunction<-function(){
eval(quote(importantVar1+1), parent.frame())
}
mainFunction<-function(importantVar1){
return(helpFunction())
}
mainFunction(importantVar1=3)
Is it possible to set a function's environment while runtime in R?
With this code you get exactly what you're looking for:
subfun0 <- function() {
e <- parent.frame()
attr(e, "name") <- "my_env"
assign("my_env", 1,
envir = parent.frame(),
inherits = FALSE, immediate = TRUE)
return(NULL)
}
subsubfun <- function() {
print(" subsubfun")
print(" =========")
print(exists("my_env"))
print(exists("my_env", parent.frame()))
print(exists("my_env", parent.frame(2)))
return(NULL)
}
subfun <- function() {
print(" subfun")
print(" ======")
print(exists("my_env"))
print(exists("my_env", parent.frame()))
print(exists("my_env", parent.frame(2)))
subsubfun()
return(NULL)
}
fun1 <- function() {
print("fun1")
print("====")
subfun0()
print(exists("my_env"))
print(exists("my_env", parent.frame()))
print(exists("my_env", parent.frame(2)))
subfun()
return(NULL)
}
fun1()
[1] "fun1"
[1] "===="
[1] TRUE
[1] FALSE
[1] FALSE
[1] " subfun"
[1] " ======"
[1] FALSE
[1] TRUE
[1] FALSE
[1] " subsubfun"
[1] " ========="
[1] FALSE
[1] FALSE
[1] TRUE
NULL
The point is that: parent.frame(2)
is not equal to parent.env(parent.frame())
Is in case of R functions a parent's parent environment of an environment also parent to the environment?
The problem is that you are actually defining all your functions in the global environment, therefore their parent is the global environment.
You get what you would expect if you define your functions inside other functions.
Look at the example below.
(Also I created a function that prints all parent environments till the global environment)
env_genealogy <- function(env){
while(!identical(env, globalenv())){
env <- parent.env(env)
print(env)
}
}
fun1 <- function() {
subfun0 <- function() {
print(" subfun0")
print(" ======")
env_genealogy(environment())
e <- parent.frame()
attr(e, "name") <- "my_env"
assign("my_env", 1,
envir = parent.frame(),
inherits = FALSE, immediate = TRUE)
return(NULL)
}
subfun <- function() {
subsubfun <- function() {
print(" subsubfun")
print(" =========")
env_genealogy(environment())
print(exists("my_env"))
print(exists("my_env", parent.frame()))
env <- parent.frame()
print(exists("my_env", parent.env(env)))
# print(parent.env(env)) # <environment: R_GlobalEnv>??
return(NULL)
}
print(" subfun")
print(" ======")
env_genealogy(environment())
print(exists("my_env"))
print(exists("my_env", parent.frame()))
env <- parent.frame()
print(exists("my_env", parent.env(env)))
subsubfun()
return(NULL)
}
print("fun1")
print("====")
env_genealogy(environment())
subfun0()
print(exists("my_env"))
print(exists("my_env", parent.frame()))
env <- parent.frame()
print(exists("my_env", parent.env(env)))
subfun()
return(NULL)
}
fun1()
[1] "fun1"
[1] "===="
<environment: R_GlobalEnv>
[1] " subfun0"
[1] " ======"
<environment: 0x000001b0e4b124d8>
<environment: R_GlobalEnv>
[1] TRUE
[1] FALSE
[1] FALSE
[1] " subfun"
[1] " ======"
<environment: 0x000001b0e4b124d8>
attr(,"name")
[1] "my_env"
<environment: R_GlobalEnv>
[1] TRUE
[1] TRUE
[1] FALSE
[1] " subsubfun"
[1] " ========="
<environment: 0x000001b0e552add0>
<environment: 0x000001b0e4b124d8>
attr(,"name")
[1] "my_env"
<environment: R_GlobalEnv>
[1] TRUE
[1] TRUE
[1] TRUE
NULL
For more details, have a look here
For a minimal example, you can look at this:
a <- function(){
i
}
a()
> #> Error in a() : object "i" not found
b <- function(){
i <- 1
a()
}
b()
> #> Error in a() : object "i" not found
d <- function(){
i <<- 1
a()
}
d()
#> [1] 1
rm(i)
f <- function(){
g <- a
i <- 2
g()
}
f()
#> Error in g() : object "i" not found
h <- function(){
l <- function() i
i <- 2
l()
}
h()
#> [1] 2
When you call a()
you get an error because i
was not defined.
Even if you define i
inside b()
you get the same error because b
's environment is not shared with a
. This is your case.
d()
works because we assign i
to the global environment with <<-
.
f()
doesn't work: even if we defined g
inside f
, we made a copy of a
which copied also its parent environment.
We get a result in h()
because l()
was defined inside. This is the case I showed you in my answer.
apply() and forceAndCall() ignoring get() from parent.frame()
The key issue here are the calling environments and the way get(inherits=TRUE)
handles them. For starters, I suggest one reads Hadley's Function environments.
More specifically, one has to understand function's enclosing and calling environments to understand what is going on.
v
is defined in the function f
calling environment, which is -- looking from the function's execution environment -- parent.frame(1L)
in the first print statement, and parent.frame(2L)
in the last two print statements.
I thought that if I supply envir=parent.frame(1L)
with inherits=TRUE
, the get
function would traverse the parent.frame()
hierarchy and eventually find v
in one of the parent calling environments. It does not. In reality it starts from the envir
environment one supplies and looks in the parent enclosing environments for v
. For the second print statement, the enclosing parent environment of parent.frame(1L)
is exactly where v
is defined, but in the last print statement, the enclosing parent environment of parent.frame(1L)
is the base
namespace where apply
is defined. Enclosing parent environment on top of that is the global environment. So v
is not found.
What we really need is a get
function that doesn't search in the parent enclosing environments hierarchy for v
, but in parent calling environments hierarchy. And that is exactly what dynget()
does.
dynGet() is somewhat experimental and to be used inside another
function. It looks for an object in the callers, i.e., the
sys.frame()s of the function. Use with caution
This function will traverse the parent calling environments sys.parents()
with sys.frame()
and look for v
in each of them. So f <- function(x) dynGet('v', inherits = TRUE)
solves the problem.
Why doesn't R look up for the specified object in the provided envrionment parents tree?
So, this is an unusually nuanced issue. There are two relevant types of environments that you need to think about here, the binding environment, or the environment that has a binding to your function, and the enclosing environment, or the environment where your function was created. In this case the binding environment is e10
, but the enclosing environment is the global environment. From Hadley Wickham's Advanced R:
The enclosing environment belongs to the function, and never changes, even if the function is moved to a different environment. The enclosing environment determines how the function finds values; the binding environments determine how we find the function.
Consider the following (executed after executing your supplied code) that demonstrates this:
eval(expression(testfun()), envir = e10)
# [1] "e10"
# Error in testfun() : object 'testvar' not found
testvar <- 600
eval(expression(testfun()), envir = e10)
# [1] "e10"
# [1] 600
Moreover, now consider:
eval(envir = e10, expr = expression(
testfun2 <- function(x) {
print(envnames::environment_name(caller_env()))
return (testvar)
}
))
eval(expression(testfun2()), envir = e10)
# [1] "e10"
# [1] 1200
I hope this clarifies the issue.
Update: Determining the Enclosing and Binding Environments
So how can we determine the binding and enclosing environments for functions such as testfun()
?
As G. Grothendieck's answer shows, the environment()
function gives you the enclosing environment for a function:
environment(e10$testfun)
# <environment: R_GlobalEnv>
To my knowledge, there isn't a simple function in base R to give you a function's binding environments. If the function you're looking for is in a parent environment, you can use pryr::where()
:
pryr::where("mean")
# <environment: base>
(There is a base
function to see if a function is in an environment, exists()
, and pryr::where()
uses it. But, it doesn't recurse through parent environments like where()
.)
However, if you're having to search through child environments, to my knowledge there isn't a function for that. But, seems pretty simple to mock one up:
get_binding_environments <- function(fname) {
## First we need to find all the child environments to search through.
## We don't want to start from the execution environment;
## we probably want to start from the calling environment,
## but you may want to change this to the global environment.
e <- parent.frame()
## We want to get all of the environments we've created
objects <- mget(ls(envir = e), envir = e)
environments <- objects[sapply(objects, is.environment)]
## Then we use exists() to see if the function has a binding in any of them
contains_f <- sapply(environments, function(E) exists(fname, where = E))
return(unique(environments[contains_f]))
}
get_binding_environments("testfun")
# [[1]]
# <environment: 0x55f865406518>
e10
# <environment: 0x55f865406518>
Related Topics
Dplyr Without Hard-Coding the Variable Names
How to Read Data with Different Separators
Should I Avoid Programming Packages with Pipe Operators
How to Have Na's Displayed First Using Arrange()
Dplyr Summarize with Subtotals
Adding 15 Business Days in Lubridate
Convert Month Year to a Date in R
Statistical Test with Test-Data
Plotting a "Sequence Logo" Using Ggplot2
Knitr: Include Figures in Report *And* Output Figures to Separate Files
Add Data to Ggvis Tooltip That's Contained in the Input Dataset But Not Directly in the Vis
Finding Non-Numeric Data in a Data Frame or Vector
Using R to Download Newest Files from Ftp-Server
Assign Names to Data Frame with As.Data.Frame Function