Environmental Disaster: Evaluating functions in multiple environments

tl;dr My functions need variables assigned to the global environment when I evaluate them in a sub environment like map, I don't want this help plzzz.

To make my functions more flexible I have made some function that take other functions as arguments, this allows the overall function framework to be the same across multiple uses and makes maintenance a lot easier. However I am getting issues with the evaluation environments. After reading this I asked a question on SO but didn't get any satisfactory answers and so hope that I might be able to solve my problem here.

library(dplyr);library(rlang)

OtherStuff <-c(10, NA)

#This quo of mean takes the vector "otherStuff" and the as yet undefined variable "vector"
EvaluateThisNow <-quo(mean(c(vector,OtherStuff), na.rm = TRUE))


MyFunc <- function(vector, TheFunction){
  #uses the captire environment which doesn't contain the object vector
  print(get_env(TheFunction))

  #Reset the enivronment of TheFunction to the current environment where vector exists
  TheFunction<- set_env(TheFunction, get_env())

  print(get_env(TheFunction))
 #The supplied function is now evaluated inside the MyFunc environment using "OtherStuff" and the variable "vector" which is defined inside MyFunc
  print(TheFunction)
  TheFunction %>%
    eval_tidy
}


MyFunc(1:4, EvaluateThisNow)

So far so good, however when I start trying to evaluate the above in a different environment it doesn't work.

#The Problem is if that process takes place inside something else then the system breaks down as now OtherStuff is not defined in the global environment but inside a local environment.
rm(OtherStuff)
set.seed(123)
1:3 %>% map(~{

#assigned inside local environment
OtherStuff <-c(sample(1:10, 1), NA)

EvaluateThis <-quo(mean(c(vector,OtherStuff), na.rm = TRUE))

 MyFunc(1:4, EvaluateThis)
 
})

#assigning the values to the global environment solves the problem, but isn't ideal and I thought that scoping would have avoided this problem.
set.seed(123)
1:3 %>% map(~{

#assigned inside local environment
OtherStuff <<-c(sample(1:10, 1), NA)

EvaluateThis <-quo(mean(c(vector,OtherStuff), na.rm = TRUE))

 MyFunc(1:4, EvaluateThis)
 
})

#The problem also occurs when using the inputs of map
OtherStuff <-c(10, NA)
c(TRUE, FALSE, TRUE) %>% map(~{

val <- .x
print(val)
EvaluateThis <-quo(mean(c(vector,OtherStuff), na.rm = val))

 MyFunc(1:4, EvaluateThis)
 
})

How do I get the scoping to work so that the function looks into the environment it is called in to find variables that aren't defined within its environment before looiking into the global environment.

1 Like

I guess this part is problematic. You don't need to reset the environment (or use <<-) in order to pass vector. rlang::eval_tidy() takes data argument, which you can pass arbitrary objects.

Does this work?:

MyFunc <- function(vector, TheFunction){
  #uses the captire environment which doesn't contain the object vector
  print(get_env(TheFunction))
  
  # (do not reset the environment)
  
  print(get_env(TheFunction))
  #The supplied function is now evaluated inside the MyFunc environment using "OtherStuff" and the variable "vector" which is defined inside MyFunc
  print(TheFunction)
  
  eval_tidy(TheFunction, data = list(vector = vector))
}
1 Like

That did work thank you! can you explain why in my version the function can find the variable in the global environment but not in the envronment it was called in? I thought the scoping would search progressively outwards to the global environment until it found a matching object?

Thanks for conformation :slight_smile:

I'm far from the expert of environment..., but IIUC, it could find the variable if it really searched "the environment it was called in". But it didn't. It directly belongs to the global environment so the caller's environment won't appear its search chain. get_env() returns the environment where the function was defined, not the one where the function is being called.

my_func_2 <- function(...) {
  e <- rlang::get_env()
  print(e)
  print(parent.env(e))
}

my_func_2()
purrr::walk(1, ~ {
  cat("the environment outside function:\n")
  e <- rlang::get_env()
  print(e)
  print(parent.env(e))
  
  cat("the environment inside function:\n")
  my_func_2()
})
#> the environment outside function:
#> <environment: 0x000000000340c040>
#> <environment: R_GlobalEnv>
#> the environment inside function:
#> <environment: 0x0000000003403bf0>
#> <environment: R_GlobalEnv>

I think I will have to just learn about environments slowly.
If you answer the SO question I'll accept it, Otherwise I can answer it and link to here.

@yutannihilation You are partially right. Please allow me to correct you a little bit.

That's not true, get_env returns the environment that the expression is currently being evaluated in:

foo <- function() {
  print(get_env())
}

> print(environment(foo))
<environment: R_GlobalEnv>
> foo()
<environment: 0x738b510>
> foo()
<environment: 0x738e1d0>

See, that for every foo() call there is a new environment assigned.

@JonnoB - the thing is all about how environments are inherited. When you call function f() it will have an environment created that inherits from the environment that is assigned to f function object, not the current environment that you are evaluating f() in. See:

x <- 42

foo <- function() {
  x <- 1
  bar()
}

bar <- function() {
  print(x)
}

> foo()
[1] 42

In the example above we are searching for value of x in bar() environment. It is not found so we are looking in its parent's environment. Parent environment of bar() is not environment of foo(), but environment of bar function object which is Global Env. We find x there and the value is 42.

Let me share a presentation that I created for one of the tech talks we had in my company. Hope it will help you understand environments better :slight_smile: : 2017-05-24 R function evaluation - Google Slides

Best,
Damian

3 Likes

Sorry, you are right. Thanks for correcting!