7

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.

4
  • 1
    Exactly why are you trying to do this? Fighting R's usual evaluation rules usually ends badly. Commented Sep 3, 2013 at 13:49
  • I was thinking that as the myfun is not used directly, it would be better to not export it, but now that I think of it and see how hard it is to use it like I wanted, maybe I'll just export and document it normally. Commented Sep 4, 2013 at 4:54
  • Originally the whole idea of not exporting myfun was probably caused by the fact that its name coindices with the function from base R, so instead of masking issues I thought it would be more clear to just not export myfun Maybe I'll just rename my function then. Commented Sep 4, 2013 at 6:56
  • That sounds like a much better idea Commented Sep 4, 2013 at 14:52

2 Answers 2

5

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

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, this works, although you and Backlin are right, it seems easier and more clear to just export both functions.
3

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.

6 Comments

Hmm, but if I do not export myfun, then using eval(mc, envir=parent.frame()) looks for myfunin global environment, and doesn't find it?
Nope, it works fine! It appears eval looks in the environments of the envir argument i.e. topfunction's enviroment, the package namespace and the global environment. Just tested it and made a demo you can look at dl.dropboxusercontent.com/u/4243933/evaltest_1.1-1.tar.gz
In your demo package lowerfunction is not exported and topfunction is part of the package and is exported. But if you remove topfunction from the package and define it in global environment (as this is supposed to be function made by user) and export lowerfunction so that user sees it (but not myfun), then this approach does not work, as eval in lowerfunction does not find myfun.
Ok, I understand now. If you want the leave the definition of topfunction to the user, and allow him/her to include both lowerfunction and myfun in it, you must define them separately and export both. If this is not an acceptable solution, can you please explain why?
The idea is that the lowerfunction can be accessed by the user, but myfun is only used inside of lowerfunction (it has no meaning elsewhere), so I though that it does not need to be exported. But it seems that in order this type of construct to work, I must export it also.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.