Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

curve3d can't find local function "fn"

Tags:

r

scoping

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.

like image 676
Jarle Tufto Avatar asked Sep 12 '18 10:09

Jarle Tufto


2 Answers

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.

like image 86
James Avatar answered Sep 23 '22 00:09

James


The eval - parse solution bypasses some worries about variable scope. This passes the value of both the variable and function directly as opposed to passing the variable or function names.

library(emdbook)

testcurve3d <- function(a) {
  fn <- eval(parse(text = paste0(
    "function(x, y) {",
    "x*y*", a,
    "}"
  )))

  eval(parse(text = paste0(
    "curve3d(", deparse(fn)[3], ")"
    )))
}

testcurve3d(2)

result

like image 39
Agriculturist Avatar answered Sep 22 '22 00:09

Agriculturist