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