R - Change function environment
If just want the function to be able to get variables from the parent frame or, if not found, from the global environment, this should work (even if I discourage it *) :
g <- function(env=parent.frame()){
z = get("x",envir=env) + get("y",envir=env) + get("a",envir=env)
return(z)
}
So, this works :
rm(list=ls(pattern='[^g]')) # to be sure the global env is empty except for g function
a = 2
f <- function(){
x = 1
y = 1
return(g())
}
f()
# returns 4
(*) I discourage this for mainly two reasons:
- The code is not very readable, and is not immediately clear what
g
does and where it takesx
,y
anda
. - From a functional programming perspective, a good practice is to make functions as "self-contained" as possible (i.e. not relying on external/global variables) and as "stateless" as possible (i.e. not keeping any "state" when called). It's much better to pass what a function needs through its arguments, and let the function be just something that receives an input (through parameters), manipulates it, and returns an output.
Calling an R function in a different environment
Move f
into env
environment(f) <- env
f()
# [1] 4
Note: Evaluation of objects across different environments is not desirable, as you have encountered here. It's best to keep all objects that you plan to interact with one another in the same environment.
If you don't want to change the environment of f
, you could put all the above into a new function.
fx <- function(f, env) {
environment(f) <- env
f()
}
fx(f, env)
# [1] 4
Distinct enclosing environment, function environment, etc. in R
TLDR:
- indeed, you can change the enclosing environment. Hadley was probably talking about packaged functions.
- the enclosing and the binding environment. You were correct.
- that's the execution environment. It only exists for the time the function runs.
Function environments
You have to distinguish 4 different environments when talking about a function:
- the binding environment is the environment where the function is found (i.e. where its name exists). This is where the actual binding of an object to its name is done.
find()
gives you the binding environment. - the enclosing environment is the environment where the function is originally created. This is not necessarily the same as the binding environment (see examples below).
environment()
gives you the enclosing environment. - the local environment is the environment within the function. You call that the execution environment.
- the parent frame or calling environment is the environment from where the function was called.
Why does this matter
Every environment has a specific function:
- the binding environment is the environment where you find the function.
- the local environment is the first environment where R looks for objects.
- the general rule is: if R doesn't find an object in the local environment, it then looks in the enclosing environment and so on. The last enclosing environment is always
emptyenv()
. - the parent frame is where R looks for the value of the objects passed as
arguments.
You can change the enclosing environment
Indeed, you can change the enclosing environment. It is the enclosing environment of a function from a package you cannot change. In that case you don't change the enclosing environment, you actually create a copy in the new environment:
> ls()
character(0)
> environment(sd)
<environment: namespace:stats>
> environment(sd) <- globalenv()
> environment(sd)
<environment: R_GlobalEnv>
> ls()
[1] "sd"
> find("sd")
[1] ".GlobalEnv" "package:stats" # two functions sd now
> rm(sd)
> environment(sd)
<environment: namespace:stats>
In this case, the second sd
has the global environment as the enclosing and binding environment, but the original sd
is still found inside the package environment, and its enclosing environment is still the namespace of that package
The confusion might arise when you do the following:
> f <- sd
> environment(f)
<environment: namespace:stats>
> find("f")
[1] ".GlobalEnv"
What happens here? The enclosing environment is still the namespace ''stats''. That's where the function is created. However, the binding environment is now the global environment. That's where the name "f" is bound to the object.
We can change the enclosing environment to a new environment e
. If you check now, the enclosing environment becomes e
, but e
itself is empty. f
is still bound in the global environment.
> e <- new.env()
> e
<environment: 0x000000001852e0a8>
> environment(f) <- e
> find("f")
[1] ".GlobalEnv"
> environment(f)
<environment: 0x000000001852e0a8>
> ls(e)
character(0)
The enclosing environment of e
is the global environment. So f
still works as if its enclosure was the global environment. The environment e
is enclosed in it, so if something isn't found in e
, the function looks in the global environment and so on.
But because e
is an environment, R calls that a parent environment.
> parent.env(e)
<environment: R_GlobalEnv>
> f(1:3)
[1] 1
Namespaces and package environments
This principle is also the "trick" packages use:
- the function is created in the namespace. This is an environment that is enclosed by the namespaces of other imported packages, and eventually the global environment.
- the binding for the function is created in the package environment. This is an environment that encloses the global environment and possible other packages.
The reason for this is simple: objects can only be found inside the environment you are in, or in its enclosing environments.
- a function must be able to find other functions(objects), so the local environment must be enclosed by possibly the namespaces of other packages it imports, the base package and lastly the global environment.
- a function must be findable from within the global environment. Hence the binding (i.e. the name of the function) must be in an environment that is enclosed by the global environment. This is the package environment (NOT the namespace!)
An illustration:
Now suppose you make an environment with the empty environment as a parent. If you use this as an enclosing environment for a function, nothing works any longer. Because now you circumvent all the package environments, so you can't find a single function any more.
> orphan <- new.env(parent = emptyenv())
> environment(f) <- orphan
> f(1:3)
Error in sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), :
could not find function "sqrt"
The parent frame
This is where it gets interesting. The parent frame or calling environment, is the environment where the values passed as arguments are looked up. But that parent frame can be the local environment of another function. In this case R looks first in that local environment of that other function, and then in the enclosing environment of the calling function, and so all the way up to the global environment, the environments of the attached packages until it reaches the empty environment. That's where the "object not found" bug sleeps.
How to use the R environment and the globalenv() function
Your card deck is stored in a vector deck
in your Global Environment.
deal <- function(){
card <- deck[1,]
assign("deck", deck[-1,], envir = globalenv())
card
}
Each function call creates it's own environment, an object assigned inside a function "lives" just inside of it. That's why you don't "see" a vector named card
in your Global Environment (unless you created one before, but this vector is uneffected by deal
functions card <- deck[1,]
statement).
So assign("deck", deck[-1])
(without the envir
argument) would be the same as
deal <- function(){
card <- deck[1,]
deck <- deck[-1,]
card
}
but this won't change your deck
outside the function. The vector deck
inside the function just exists inside the function. To change the deck
outside the function, you have to tell R
where to change it. So that's why assign("deck", deck[-1,], envir = globalenv())
is used.
So let's start over with your function deal
:
card <- deck[1,]
assigns the first element of deck
to card
. But wait! deck
doesn't exists inside the function? So how is this possible? If the object isn't found inside the function, R
looks one level up, in your case most likely the Global Environment. So there R finds an object/vector named deck
and does the assignment. Now we have an object/vector named card
that exists inside the function.
For further understanding, take a look at Chapter 6: Functions in Advanced R.
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)
How can I reference the local environment within a function, in R?
To get the current environment, just call environment()
.
In general, sys.frame
returns any of the environments currently on the call stack, and sys.nframe
returns the current depth of the call stack. sys.frames
returns a list of all environments on the call stack.
environment(f)
returns the closure environment for a function f
(where it will look for functions and global variables).
parent.env(e)
returns the parent environment where it will look if a symbol is not found in e
.
f <- function() {
function() list(curEnv=environment(), parent=parent.env(environment()),
grandParent=parent.env(parent.env(environment())), callStack=sys.frames(),
callStackDepth=sys.nframe())
}
g <- function(f, n=2) if (n>2) g(f, n-1) else f()
floc <- f() # generate a local function
g(floc, 3) # call it
This will call the local function floc
with a stack depth of 3. It returns a list with the current environment, it's parent (the local environment in f
), and it's grand parent (where f
was defined, so globalenv
). It also returns the list of stack frames (environments). These are the environments for the recursive calls in g
(except the last one which is the current environment of floc
).
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>
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())
Related Topics
Count Unique Combinations of Values
Dplyr - Mutate Dynamically Named Variables Using Other Dynamically Named Variables
Removing Particular Character in a Column in R
Vary the Color Gradient on a Scatter Plot Created with Ggplot2
Error in If/While (Condition):Argument Is Not Interpretable as Logical
Enclosing Variables Within for Loop
Export Both Image and Data from R to an Excel Spreadsheet
Why Does As.Matrix Add Extra Spaces When Converting Numeric to Character
How to Pass Individual 'Curvature' Arguments in 'Ggplot2' 'Geom_Curve' Function
Repeat Vector to Fill Down Column in Data Frame
Pass R Variable to Rodbc's SQLquery with Multiple Entries
R - Replace Specific Value Contents with Na
Installing Ggplot2 Package on Ubuntu
Split or Separate Uneven/Unequal Strings with No Delimiter
Aggregating Unique Values in Columns to Single Dataframe "Cell"
How to Run a High Pass or Low Pass Filter on Data Points in R