2

I have a situation where I have a function whose environment I want to change. This function is in a local custom R package.

Here's my setup. I have an R package project called testpkg with one function in /R called do_stuff.R:

#' A function that clearly does stuff
#' @export
do_stuff <- function() {
  Sys.sleep(1)
  "Done"
}

In an interactive script I'm trying to assign this function to another environment. I want to use the assign function so I can do this programmatically. Here's what I've been trying:

devtools::load_all()
env <- new.env()
assign("do_stuff", do_stuff, env)

# See if it worked (nope, environment is namespace:testpkg)
> env$do_stuff
# function() {
#   Sys.sleep(1)
#   "Done"
# }
# <environment: namespace:testpkg>

If I use environment()<- it does seem to work:

devtools::load_all()
environment(do_stuff) <- new.env()

# See if it worked (yes, environment is different)
> do_stuff
#function() {
#  Sys.sleep(1)
#  "Done"
# }
# <environment: 0x7f9d1c3a8fd8>

But this isn't ideal since I need this to work programmatically. I realize this seems like a pretty strange thing to do. Essentially, I need to make my custom functions transportable to work with the future package for async, but that's beside the point.

Will Hipson
  • 366
  • 2
  • 9
  • 2
    Have you seen: https://stackoverflow.com/questions/12279076/r-specify-function-environment? The environment where a given variable name lives is different than the environment where a function was defined. When you do `assign("do_stuff", do_stuff, env)` you are only creating a new variable name and not changing any of the properties of the function itself. The function still retains it's enviroment() property that it uses to resolve variables. It's not clear what your exact question is here. You are kind of just describing exactly how things work in R. – MrFlick May 04 '23 at 18:52
  • Maybe a better question is can I use `environment(x) <- new.env()` programmatically? For example, I want to iterate over all custom package functions. – Will Hipson May 04 '23 at 19:03
  • Is there a reason you can't put that in your own function to make it "programatic"? I'm not sure I understand where the problem is. If you had a list of function you could create a new list with new environments with `funs <- list(f1, f2, f3); enew <- new.env(); newfuns <- lapply(funs, function(x) {environment(x) <- enew; x})` This doesn't change the original functions, it returns new ones. – MrFlick May 04 '23 at 20:34
  • 1
    As @MrFlick said, you don't appear to understand what `environment(x)` actually does. I think Hadley Wickhams book on Advanced R Programming does a pretty good job of explaining things. You should read it, then ask questions from a more informed starting point. – user2554330 May 05 '23 at 00:09
  • Thanks for the suggestions. I originally thought that if `x` is a function then using `assign("x", x, env)` and `environment(x) <- env` were synonymous, but clearly I've misunderstood. In R environments are a bit esoteric. – Will Hipson May 05 '23 at 11:36

1 Answers1

1

In R, functions are lexically scoped. This means that they look up free variables in the environment where they are defined. In order for that to work, functions need to keep track of that environment. This is the same same for both anonymous and named functions. You can retrieve/set this environment using the envionment() and environment<-() functions. This is just the like class() and names() functions. They alter properties of the object itself.

When you use assign(), you are just pointing a particular symbol/name to an object. Different environments may assign different values to the same name. This has nothing to do with the environment property attached to a function when it was created. A named function in one environment can easily point to a different environment to resolve free variable names.

Here's a demonstration of the way things work

other <- new.env()
other$x <- 15
x <- 2
# The global environment and the `other` enviroment
# now have two different values for `x`

foo <- function(num) x+num
# Note that "x" is a free variable in the function
# by default the environment() of foo will be the
# global environment since that was where it was first
# defined.

foo(5)
# [1] 7
other$foo(5)
# Error: attempt to apply non-function
# The function doesn't exist yet in other

assign("foo", foo, envir=other)
other$foo(5)
# [1] 7
# even though we assigned the name "foo" to point
# to the "foo" function, it still looks for "x" in 
# the global environment

environment(foo) <- other
foo(5)
# [1] 20
# Note how changing the environment property of the function 
# changed where it looked up the value for `x`. We now use
# the `x` value from `other`

other$foo(5)
# [1] 7
# but the name "foo" in the other environment still points to 
# the original version of "foo" that has the environment set 
# to the current global environment
MrFlick
  • 195,160
  • 17
  • 277
  • 295