How can I manage environments to wrap functions and avoid environment issues? - r

Here are 4 functions which mimics a family of functions from basic to advanced.
f1 <- function(expr,envir) {
eval(expr,envir)
}
f2 <- function(expr) {
expr <- substitute(expr)
f1(expr,parent.frame())
}
f3 <- function(x) {
lapply(1:3, function(i) {
f2(x+i)
})
}
f4 <- function(...) {
f3(...)
}
f1 is the fundamental one, f2 calls f1 with an expression, f3 iteratively calls f2 with x defined in its body frame.
Calling f3 yields
> f3(1)
[[1]]
[1] 2
[[2]]
[1] 3
[[3]]
[1] 4
which has no problem because the x is evaluated correctly in parent.frame() of f2. However, if a wrapper function f4 is called, an error occurs:
> f4()
Error in eval(expr, envir, enclos) :
argument "x" is missing, with no default
I inspect the parent environments local to f2 in line and find that parent.frame(3) contains x: missing.
Is there a way or good practice to manage the environments so that I can wrap functions like f4 without worrying too much about the parent environment issues?

Related

Why is this simple function not working?

I first defined new variable x, then created function that require x within its body (not as argument). See code below
x <- c(1,2,3)
f1 <- function() {
x^2
}
rm(x)
f2 <- function() {
x <- c(1,2,3)
f1()
}
f(2)
Error in f1() : object 'x' not found
When I removed x, and defined new function f2 that first define x and then execute f1, it shows objects x not found.
I just wanted to know why this is not working and how I can overcome this problem. I do not want x to be name as argument in f1.
Please provide appropriate title because I do not know what kind of problem is this.
You could use a closure to make an f1 with the desired properties:
makeF <- function(){
x <- c(1,2,3)
f1 <- function() {
x^2
}
f1
}
f1 <- makeF()
f1() #returns 1 4 9
There is no x in the global scope but f1 still knows about the x in the environment that it was defined in.
In short: Your are expecting dynamic scoping but are a victim of R's lexical scoping:
dynamic scoping = the enclosing environment of a command is determined during run-time
lexical scoping = the enclosing environment of a command is determined at "compile time"
To understand the lookup path of your variable x in the current and parent environments try this code.
It shows that both functions do not share the environment in with x is defined in f2 so it can't never be found:
# list all parent environments of an environment to show the "search path"
parents <- function(env) {
while (TRUE) {
name <- environmentName(env)
txt <- if (nzchar(name)) name else format(env)
cat(txt, "\n")
if (txt == "R_EmptyEnv") break
env <- parent.env(env)
}
}
x <- c(1,2,3)
f1 <- function() {
print("f1:")
parents(environment())
x^2
}
f1() # works
# [1] "f1:"
# <environment: 0x4ebb8b8>
# R_GlobalEnv
# ...
rm(x)
f2 <- function() {
print("f2:")
parents(environment())
x <- c(1,2,3)
f1()
}
f2() # does not find "x"
# [1] "f2:"
# <environment: 0x47b2d18>
# R_GlobalEnv
# ...
# [1] "f1:"
# <environment: 0x4765828>
# R_GlobalEnv
# ...
Possible solutions:
Declare x in the global environment (bad programming style due to lack of encapsulation)
Use function parameters (this is what functions are made for)
Use a closure if x has always the same value for each call of f1 (not for beginners). See the other answer from #JohnColeman...
I strongly propose using 2. (add x as parameter - why do you want to avoid this?).

R functions: passing arguments with ellipsis

I have a question regarding base R usage. It might be asked before, however I wasn't able to find solution to my problem.
I have a function that calls another function. Arguments to second function are passed using ellipsis (...). However, I get error message: object "OBJECT" not found.
f1 <- function(a, ...) {
print(a)
f2(...)
}
f2 <- function(...) {
print(b == TRUE)
print(runif(c))
}
f1(2, b = FALSE, c = 2)
Which gives me: Error in print(b == TRUE) : object 'b' not found.
I know that it is possible to get around this problem using args <- list(...) and then calling each argument separately, but I imagine that this gets complicated when having lots of arguments (not only two).
Question
How to pass arguments from f1 to f2 using ellipsis?
So the ellipses are used to save you specifying all the arguments of f2 in the arguments of f1. Though when you declare f2, you still have to treat it like a normal function, so specify the arguments b and c.
f1 <- function(a, ...) {
print(a)
f2(...)
}
# Treat f2 as a stand-alone function
f2 <- function(b, c) {
print(b == TRUE)
print(runif(c))
}
f1(2, b=FALSE, c=2)
[1] 2
[1] FALSE
[1] 0.351295 0.9384728

Passing variables through environments

I have the following two functions :
f1<-function(){
txt<-1234
f2(where="txt")
}
f2<-function(where){
foo<-eval(parse(text = where))*2
return(foo)
}
When calling f1(), I would expect it to return 2468. However
> f1()
Error in eval(expr, envir, enclos) : object 'txt' not found
I do not understand why, and specifically why f2 does not know txt. Of course it is not defined in its own environment, but it is defined in the caller environment (in f1), I thought everything defined within f1 should be visible to f2 ?
Of course, if in f1 I have
txt<<-1234
then
> f1()
[1] 2468
But I would rather avoid global assignments (in real code, I do not want to have stray global objects...)
So the question is, how can I make txt (defined in f1) visible to f2 ?
Thanks
(and in case you wonder, the real-life f2 is more complex, such that passing the name of a variable makes sense; in any case it is a function written by somebody else on which I have no control, so the solution should come from the f1 side).
1) The problem is really with f2, not with f1, so f2 should be fixed. One would normally define f2 to pass the environment explicitly. With this code f1 would work as is.
f2 <- function(where, envir = parent.frame()) {
eval(parse(text = where), envir = envir)*2` .
}
2) The following is less desirable; however, if we did not have control over f2 then we could do this in f1 (where now f2 is unchanged from the question):
f1 <- function() {
txt <- 1234
environment(f2) <- environment()
f2(where = "txt")
}
3) A third option is to define f2 within f1:
f1 <- function(){
f2 <- function(where) eval(parse(text = where))*2
txt <- 1234
f2(where = "txt")
}
f1()
Specify the envir argument in eval as parent.frame()
f2<-function(where){
foo<-eval(parse(text = where), envir= parent.frame())*2
return(foo)
}
f1()
#[1] 2468

Why wrapper functions do not work as expected?

Here are four functions, the latter ones wraps the former ones.
a <- 0
f1 <- function(expr) {
a1 <- 1
eval(expr)
}
f2 <- function(expr) {
a2 <- 2
f1(expr)
}
f3 <- function(expr) {
a3 <- 3
f2(expr)
}
f4 <- function(expr) {
a4 <- 4
f3(expr)
}
Do the following experienments:
> f4(a)
0
which works as expected. But if we call
f4(a4)
Error in eval(expr) : object 'a4' not found
> f4(a3)
Error in eval(expr) : object 'a3' not found
...
> f2(a2)
Error in eval(expr) : object 'a2' not found
> f2(a1)
Error in eval(expr) : object 'a1' not found
> f1(a1)
Error in eval(expr) : object 'a1' not found
I inspect the local environment and parent environment of each function body f3's parent frame is f4's local environment, ... , f1's parent is f2's body. Is it a clear explanation why this happens? And how can I get rid of this problem to make the code work for the purpose that the function call should allow subsequent functions (like f3) to find the defined symbols (e.g. a4)?
I strongly recommend you spend some time reading Advanced R: Environments.
First of all, when I run f1(a1) I get "object 'a1' not found" as well; not "1" as you get above.
The issue is that by default R resolves variables using the enclosing environment of a function. The enclosing environment of a function is determined when the function is defined, not when the function is called. Therefore it doesn't go up the call chain to resolve variable names. You can explicitly look in a calling parent with the parent.frame() environment, but these environments do not chain together in nested function calls.
In the same way that get() will loop up a variable by walking up the enclosing parent environments, you can make your own function to walk up the calling environments and see which variables are available.
call.get <- function(val) {
for(i in 1:sys.nframe()) {
if (exists(val, envir=sys.frame(i), inherits=F)) {
return(get(val, envir=sys.frame(i)))
}
}
return(NULL)
}
call.ls <- function(val) {
vars<-lapply(1:sys.nframe(), function(i) ls(envir=parent.frame(i)))
return(sort(unique(unlist(vars))))
}
Then if you do something like
f1 <- function(expr) {
a1 <- 1
call.ls()
}
f2 <- function(expr) {
a2 <- 2
f1(expr)
}
f3 <- function(expr) {
a3 <- 3
f2(expr)
}
f4 <- function(expr) {
a4 <- 4
f3(expr)
}
f4(1)
You will get
"a1" "a2" "a3" "expr" "FUN" "val" "X"
and you can use
call.get("a3")
to get one of those variables from a parent calling frame.
But another problem you have is you are triggering evaluation of the expr argument when you call the sub-function. When you do
f2 <- function(expr) {
a2 <- 2
f1(expr)
}
That evaluates expr in the f2 environment and passes the result to f1. You are losing the evaluation at that point. The easiest way to pass through a lazy-evaluation is to use "...". Something like
f1 <- function(...) {
a1 <- 1
expr<-deparse(substitute(...))
call.get(expr)
}
f2 <- function(...) {
a2 <- 2
f1(...)
}
f2(a1)
# [1] 1
f2(a2)
# [1] 2
Otherwise you need to more explicitly pass the expression with a do.call
f1 <- function(expr) {
a1 <- 1
expr<-deparse(substitute(expr))
call.get(expr)
}
f2 <- function(expr) {
expr<-substitute(expr)
a2 <- 2
do.call(f1, list(expr))
}
f2(a1)
# [1] 1
f2(a2)
# [1] 2

R - Evaluate a nested function in an environment

I am trying to run a chunk of R code in a sandbox-ed fashion, by loading all the necessary dependencies (functions and data) into a new environment and evaluating an expression within that environment. However, I'm running into trouble with functions calling other functions in the environment. Here's a simple example:
jobenv <- new.env(parent=globalenv())
assign("f1", function(x) x*2, envir=jobenv)
assign("f2", function(y) f1(y) + 1, envir=jobenv)
expr <- quote(f2(3))
Using eval on expr fails since f2 can't find f1
> eval(expr, envir=jobenv)
Error in f2(3) : could not find function "f1"
whereas explicitly attaching the environment works
> attach(jobenv)
> eval(expr)
[1] 7
I'm probably missing something obvious, but I couldn't find any permutation of the eval call that works. Is there a way to get the same effect without attaching the environment?
There are a number of ways of doing this, but I kind of like this one:
jobenv <- new.env(parent=globalenv())
local({
f1 <- function(x) x*2
f2 <- function(y) f1(y) + 1
}, envir=jobenv)
## Check that it works
ls(jobenv)
# [1] "f1" "f2"
local(f2(3), envir=jobenv)
# [1] 7
eval(quote(f2(3)), envir=jobenv)
# [1] 7
Scope is defined when the function is created, not when it's called. See section 10.7 of the Introduction to R manual.
This seems a bit odd to me, but you get the same behavior even if you avoid assign all together and just use $<-.
jobenv <- new.env(parent=globalenv())
jobenv$f1 <- function(x) x*2
jobenv$f2 <- function(y) f1(y) + 1
expr <- quote(f2(3))
eval(expr, envir=jobenv)
This seems to be because the enclosing environment of f1 and f2 is the global environment. I would have expected it to be jobenv.
> environment(jobenv$f1)
<environment: R_GlobalEnv>
> environment(jobenv$f2)
<environment: R_GlobalEnv>
One solution is to explicitly set the environment of each function... but there has to be an easier way.
> environment(jobenv$f1) <- jobenv
> environment(jobenv$f2) <- jobenv
> eval(expr, envir=jobenv)
[1] 7

Resources