Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

S4 method fails when also set in another package

Tags:

r

s4

I have encountered a curious problem when I try to set a method that was also defined in another package. An example package which demonstrates this problem can be found here.

The key is the typeof method that I try to set. I use the setMethod function for typeof and it works when I build the package and try it on a trivial S4 class.

x <- new("A", x = 2)
typeof(x)
[1] "typeof was called"
[1] "myClassA"

However, if I load another package that previously also set typeof (e.g. the bigmemory) prior to loading the s4test package it continues to work fine if called directly.

library(bigmemory)
library(s4test)
x <- new("A", x = 2)
typeof(x)
[1] "typeof was called"
[1] "myClassA"

But, if the typeof method is called internally by another method (e.g. nrow see here) then it fails and only returns S4

nrow(x)
[1] "A"
attr(,"package")
[1] "s4test"
Function: typeof (package base)
x="ANY"
x="big.matrix"
x="myClass"

A connection with                      
description "stdout"  
class       "terminal"
mode        "w"       
text        "text"    
opened      "opened"  
can read    "no"      
can write   "yes"     
[1] "S4"
like image 277
cdeterman Avatar asked Jun 20 '18 16:06

cdeterman


1 Answers

Juan Antonio was on the right track, but it has nothing to do with Depends and Imports.

The answer is in the documentation entry for Methods_for_Nongenerics (which applies to typeof because it is not a generic function in base):

In writing methods for an R package, it's common for these methods to apply to a function (in another package) that is not generic in that package; that is, there are no formal methods for the function in its own package, although it may have S3 methods. The programming in this case involves one extra step, to call setGeneric() to declare that the function is generic in your package.

Calls to the function in your package will then use all methods defined there or in any other loaded package that creates the same generic function. Similarly, calls to the function in those packages will use your methods.

The original version, however, remains non-generic. Calls in that package or in other packages that use that version will not dispatch your methods except for special circumstances...

You can see the effects of this by running the following in a clean R session:

environment(typeof)
<environment: namespace:base>

library(s4test)
environment(typeof)
<environment: 0x0000000017ca5e58>

After loading the package, the typeof function has been made generic, but not in its original environment. You can also see the enclosing environment of the new generic's environment:

parent.env(environment(typeof))
<environment: namespace:base>

When calling a function, R first looks in the current environment, and looks in enclosing environments when something is not found. The nrow function is part of the base package (and it also wasn't generic, although that's not relevant here), which means that it will find its own non-generic typeof before seeing the generic one.

The documentation of Methods_for_Nongenerics explains different scenarios, but for your case, you could actually do the following:

setMethod('nrow', signature(x="myClass"),
          function(x) {
              # find the generic version from the global environment and bind it here
              typeof <- get("typeof", .GlobalEnv)
              switch(typeof(x),
                     "myClassA" = "A rows",
                     "myClassB" = "B rows")
          }
)

nrow(x)
[1] "typeof was called"
[1] "A rows"
like image 86
Alexis Avatar answered Nov 15 '22 23:11

Alexis