Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to evaluate arguments of a function call inside other function in R

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.

like image 462
Jouni Helske Avatar asked Sep 03 '13 07:09

Jouni Helske


2 Answers

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

like image 167
hadley Avatar answered Oct 17 '22 19:10

hadley


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")

Update

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.

like image 45
Backlin Avatar answered Oct 17 '22 17:10

Backlin