Search code examples
rscoping

curve3d can't find local function "fn"


I'm trying to use the curve3d function in the emdbook-package to create a contour plot of a function defined locally inside another function as shown in the following minimal example:

library(emdbook)
testcurve3d <- function(a) {
  fn <- function(x,y) {
    x*y*a
  }
  curve3d(fn(x,y))
}

Unexpectedly, this generates the error

> testcurve3d(2)
 Error in fn(x, y) : could not find function "fn" 

whereas the same idea works fine with the more basic curve function of the base-package:

testcurve <- function(a) {
  fn <- function(x) {
    x*a
  }
  curve(a*x)
}
testcurve(2)

The question is how curve3d can be rewritten such that it behaves as expected.


Solution

  • You can temporarily attach the function environment to the search path to get it to work:

    testcurve3d <- function(a) {
      fn <- function(x,y) {
        x*y*a
      }
      e <- environment()
      attach(e)
      curve3d(fn(x,y))
      detach(e)
    }
    

    Analysis

    The problem comes from this line in curve3d:

    eval(expr, envir = env, enclos = parent.frame(2))
    

    At this point, we appear to be 10 frames deep, and fn is defined in parent.frame(8). So you can edit the line in curve3d to use that, but I'm not sure how robust this is. Perhaps parent.frame(sys.nframe()-2) might be more robust, but as ?sys.parent warns there can be some strange things going on:

    Strictly, sys.parent and parent.frame refer to the context of the parent interpreted function. So internal functions (which may or may not set contexts and so may or may not appear on the call stack) may not be counted, and S3 methods can also do surprising things.

    Beware of the effect of lazy evaluation: these two functions look at the call stack at the time they are evaluated, not at the time they are called. Passing calls to them as function arguments is unlikely to be a good idea.