I'm looking for a way to call a function that is not influenced by other objects in .GlobalEnv
.
Take a look at the two functions below:
y = 3
f1 = function(x) x+y
f2 = function(x) {
library(dplyr)
x %>%
mutate(area = Sepal.Length *Sepal.Width) %>%
head()
}
In this case:
f1(5)
should fail, becausey
is not defined in the function scopef2(iris)
should pass, because the function does not reference variables outside its scope
Now, I can overwrite the environment of f1
and f2
, either to baseenv()
or new.env(parent=environment(2L))
:
environment(f1) = baseenv()
environment(f2) = baseenv()
f1(3) # fails, as it should
f2(iris) # fails, because %>% is not in function env
or:
# detaching here makes `dplyr` inaccessible for `f2`
# not detaching leaves `head` inaccessible for `f2`
detach("package:dplyr", unload=TRUE)
environment(f1) = new.env(parent=as.environment(2L))
environment(f2) = new.env(parent=as.environment(2L))
f1(3) # fails, as it should
f2(iris) # fails, because %>% is not in function env
Is there a way to overwrite a function's environment so that it has to be self-sufficient, but it also always works as long as it loads its own libraries?
The problem here is, fundamentally, that
library
and similar tools don’t provide scoping, and are not designed to be made to work with scopes:1 Even thoughlibrary
is executed inside the function, its effect is actually global, not local. Ugh.Specifically, your approach of isolating the function from the global environment is sounds; however,
library
manipulates thesearch
path (viaattach
), and the function’s environment isn’t “notified” of this: it will still point to the previous second search path entry as its grandparent.You need to find a way of updating the function environment’s grandparent environment when
library
/attach
/… ist called. You could achieve this by replacinglibrary
etc. in the function’s parent environment with your own versions that calls a modified version ofattach
. Thisattach2
would then not only call the originalattach
but also relink your environment’s parent.1 As an aside, ‹modules› fixes all of these problems. Replacing
library(foo)
bymodules::import_package('foo', attach = TRUE)
in your code makes it work. This is because modules are strongly scoped and environment-aware.