I'm having trouble of understanding how to work with nested function calls and argument evaluations.
Here's a simple example. I have a top-level function topfunction
with one numeric argument. Inside of topfunction
I call another function lowerfunction
which argument is a call to a function defined inside of lowerfunction
.
topfunction<-function(x){
lowerfunction(myfun(first=x[1],second=x[2],third=if(length(x)>2) x[3]))
}
lowerfunction<-function(mycall){
myfun<-function(first,second=0,third=NULL){
print(first)
print(second)
print(third)
}
mc<-match.call(definition = myfun, call = match.call()[[2]])
eval(mc)
}
Inside of lowerfunction
I capture the function call with match.call
, and try to evaluate the call. But as variable x
is only defined in the environment of topfunction
, the evaluation fails:
topfunction(x=1:3)
Error in print(first) : object 'x' not found
I know that I could change the line
lowerfunction(myfun(first=x[1],second=x[2],third=if(length(x)>2) x[3]))
as
lowerfunction(substitute(myfun(first=x[1],second=x[2],third=if(length(x)>2) x[3])))
in topfunction
, but in my real application the topfunction
is constructed by the user, so the solution should happen somehow in the lowerfunction
or even in the myfun
level. But as they have already lost the information about x
, I don't know if that can be accomplished?
In the real application the topfunction
constructs the model using lowerfunction
and computes its likelihood, whereas the argument of lowerfunction
is a formula which can contain function calls, which will be evaluated via eval
. These functions are only defined inside the lowerfunction
. Also, lowerfunction
can also be called directly, i.e.
x<-1:3
lowerfunction(myfun(first=x[1],second=x[2],third=if(length(x)>2) x[3]))
# or
lowerfunction(myfun(first=x1,second=2)
So solutions which add x
to the argument list of lowerfunction
are not applicable in general.
So the problem is that eval
should take the definition of myfun
from one environment (package namespace, or in this case from the environment of lowerfunction
), and evaluate the arguments of myfun
in other environment i.e in the environment of topfunction
.
This is a relatively straightforward problem, but because you're doing very non-standard evaluation you'll need to create a new environment and all ensure all the objects you need are accessible from that environment.
g <- function(x){
f1(f2(x[1], x[2], if(length(x) > 2) x[3]))
}
f1 <- function(mycall, parent = parent.frame()) {
# Parent contains x
# New environment that contains f2 and inherits from the parent
env <- new.env(parent = parent)
env$f2 <- function(first, second = 0,third = NULL) {
print(first)
print(second)
print(third)
}
# More idiomatic way of getting unevaluated expression
expr <- substitute(mycall)
eval(expr, env)
}
g(1:3)
I describe similar techniques in my chapter on domain specific languages
Lift myfun
out of lowerfun
and modify the eval
call as below. When making a package, if you do not export myfun
it will not be accessible directly from R_GlobalEnv
, but it can still be called from within lowerfun
.
topfunction <- function(x){
lowerfunction(myfun(first=x[1], second=x[2], third=if(length(x)>2) x[3]))
}
lowerfunction<-function(mycall){
mc <- match.call(definition = myfun, call = match.call()[[2]])
eval(mc, envir=parent.frame())
}
myfun <- function(first, second=0, third=NULL){
print(first)
print(second)
print(third)
}
Example run:
> topfunction(1:3)
[1] 1
[1] 2
[1] 3
Off topic: myfun
can still be accessed from R_GlobalEnv
by calling
getFromNamespace("myfun", "mypackage")
If you really want to keep myfun
within lowerfunction
, to preserve a conceptual point, you would need to merge the environments of topfunction
and lowerfunction
and evaluate mc
there, but I do not know if that is possible (which it turned out to be, see @hadley's answer).
However, you can copy the variables not found in lowerfunction
's environtment (i.e. x
) from topfunction
's environment prior to evaluation. Thanks to the lazy evaluation, this does not affect memory usage unless they are modified.
lowerfunction<-function(mycall){
myfun <- function(first, second=0, third=NULL){
print(first)
print(second)
print(third)
}
mc <- match.call(definition = myfun, call = match.call()[[2]])
x <- get("x", parent.frame())
eval(mc)
}
However again, since you do not know what objects the user will incorporate into topfunction
, you cannot hard code it as above but must do it by extracting all names from mc
and copy them via assign
. It is possible, but I recommend you save yourself the trouble and export both lowerfunction
and myfun
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With