[Rd] Environment when NextMethod is used

Winston Chang
Sun, 02 Sep 2012 04:25:15 -0000

I'm running into some hard-to-understand behavior with the evaluation
environment when NextMethod is used. I'm using square-bracket indexing
into objects, and the evaluation environment of the expression inside
the square brackets seems to change depending on what kind of
comparison operators are used.

This behavior happens when the following conditions are met (this is
what I've found; I'm sure that these aren't necessary and sufficient
conditions):
- I call a function from an attached package.
- The function uses square bracket indexing with a class that has its
own definition of the operator, such as `[.factor` or `[.POSIXct`. (If
a vector of numerics is used, the error doesn't happen.)
- The indexing function uses NextMethod("[").
- An S3 method is used within the square brackets. (When a regular
function is used, there's no error.)
- The S3 method is from a package that is an import for the original
function's package, but this package is not attached. (If the package
is attached, then the error doesn't happen because R finds the method
in the standard search path.)
- An operator like == is used. (If the %in% operator is used, the
error doesn't happen.)


This may sound very abstract. I've created a sample package that
illustrates the behavior. The package is called envtest, and it has a
function called envtest(), which uses an S3 method from the nlme
package. nlme is listed as an import.

You can either clone the repository here:
  https://github.com/wch/envtest
Or you can install it with devtools, using:
  library(devtools)
  dev_mode()
  install_github('envtest', 'wch')



The envtest() function tries to index into a factor in different ways,
and prints the output for each one. This is the content of the
function. (If you load it from the global environment, it won't have
the same error, since the issue has to do with an import):

envtest <- function() {
  dat <- data.frame(x = 0, y = 0)
  f <- factor(c("a", "b"))

  # Print the starting data
  cat("\nf                                                : ")
  cat(f)

  cat("\n\nTests with %in% operator ----------------------------")

  # OK
  cat('\n"x" %in% Names(y ~ x, data = dat)                : ')
  cat("x" %in% Names(y ~ x, data = dat))

  # OK: Save boolean values to idx, then use f[idx]
  cat('\nidx <- "x" %in% Names(y ~ x, data = dat); f[idx] :
')
  cat({idx <- "x" %in% Names(y ~ x, data = dat); f[idx]})

  # OK: Use the expression with S3 function Names directly inside of []
  cat('\nf["x" %in% Names(y ~ x, data = dat)]             : ')
  cat(f["x" %in% Names(y ~ x, data = dat)])


  cat("\n\nTests with == operator ------------------------------")

  # OK
  cat('\n"x" == Names(y ~ x, data = dat)                  : ')
  cat("x" == Names(y ~ x, data = dat))

  # OK: Save boolean values to idx, then use f[idx]
  cat('\nidx <- "x" == Names(y ~ x, data = dat); f[idx]   :
')
  cat({idx <- "x" == Names(y ~ x, data = dat); f[idx]})

  # Error: Use the expression with S3 function Names directly inside of []
  cat('\nf["x" == Names(y ~ x, data = dat)]               : ')
  cat(f["x" == Names(y ~ x, data = dat)])

  invisible()
}



This is what happens when I run the envtest() function. All the
indexing operations work, except the last one, where, inside the
square brackets, the == operator is used, and it calls the S3 method
from an imported package.

> library(envtest)
> envtest()
f                                                : 1 2

Tests with %in% operator ----------------------------
"x" %in% Names(y ~ x, data = dat)                : TRUE
idx <- "x" %in% Names(y ~ x, data = dat); f[idx] : 1 2
f["x" %in% Names(y ~ x, data = dat)]             : 1 2

Tests with == operator ------------------------------
"x" == Names(y ~ x, data = dat)                  : FALSE TRUE
idx <- "x" == Names(y ~ x, data = dat); f[idx]   : 2
f["x" == Names(y ~ x, data = dat)]               : Error in Names(y ~
x, data = dat) : could not find function "Names"


When I set options(error=recover), it's possible to investigate the
environment where it's trying to evaluate the expression Names(....),
when it runs into the error:

Enter a frame number, or 0 to exit

1: envtest()
2: envtest.r#40: cat(f["x" == Names(y ~ x, data = dat)])
3: f["x" == Names(y ~ x, data = dat)]
4: `[.factor`(f, "x" == Names(y ~ x, data = dat))
5: NextMethod("[")
6: Names(y ~ x, data = dat)

Selection: 5

Browse[1]> environment()
<environment: 0x104421a78>
Browse[1]> parent.env(environment())
<environment: namespace:base>
Browse[1]> parent.env(parent.env(environment()))
<environment: R_GlobalEnv>


When == is used, it tries to evaluate the expression Names(....) in
the environment namespace:base, and it fails because it can't find the
function.
However, when %in% is used, it tries to evaluate the expression
Names(....) in the environment namespace:nlme, which makes more sense
to me.


Is this expected behavior? And if so, could someone explain why it
should be expected? I'm confused as to why the evaluation environment
should change when a certain narrow set of conditions is met.


-Winston