Search code examples
reval

Is there a way to make match.call + eval combination work when called from a function?


I am using a package that has 2 functions which ultimately look like the following:

pkgFun1 <- function(group) {
  call <- match.call()
  pkgFun2(call)
}

pkgFun2 <- function(call) {
  eval(call$group)
}

If I just call pkgFun1(group = 2), it works fine. But I want to call it from a function:

myFun <- function(x) {
  pkgFun1(group = x)
}
myFun(x = 2)
## Error in eval(call$group) : object 'x' not found

Is there any way to avoid this error, if I can't modify the package functions, but only myFun?

There are similar questions, such as Issue with match.call or Non-standard evaluation in a user-defined function with lapply or with in R, but my particular issue is that I can't modify the part of code containing the eval call.


Solution

  • It's pkgFun2 that is wrong, so I think you're out of luck without some weird contortions. It needs to pass the appropriate environment to eval(); if you can't modify it, then you can't fix it.

    This hack might appear to work, but in real life it doesn't:

    pkgFun1 <- function(group) {
      call <- match.call()
      f <- pkgFun2
      environment(f) <- parent.frame()
      f(call)
    }
    

    With this, you're calling a copy of pkgFun2 modified so its environment is appropriate to evaluate the call. It works in the test case, but will cause you untold grief in the future, because everything that is not local in pkgFun2 will be searched for in the wrong place. For example,

    myFun <- function(x) {
      eval <- function(...) print("Gotcha!")
      pkgFun1(group = x)
    }
    myFun(x = 2)
    # [1] "Gotcha!"
    

    Best is to fix pkgFun2. Here's one fix:

    pkgFun1 <- function(group) {
      call <- match.call()
      pkgFun2(call, parent.frame())
    }
    
    pkgFun2 <- function(call, envir) {
      eval(call$group, envir = envir)
    }
    

    Edited to add: Actually, there is another hack that is not so weird that should work with your original pkgFun1 and pkgFun2. If you force the evaluation of x to happen in myFun so that pkgFun1 never sees the expression x, it should work. For example,

    myFun <- function(x) {
      do.call("pkgFun1", list(group = x))
    }
    

    If you do this, then after myFun(2), the pkgFun1 variable call will be pkgFun1(group = 2) and you won't get the error about x.