8

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"
cdeterman
  • 19,630
  • 7
  • 76
  • 100
  • Is this behaviour consistent before and after your method is invoked for the first time? Sometimes with internal generics, problems go away after the method gets registered (which happens at first calling). – JDL Jun 22 '18 at 11:50
  • @JDL, yes it is consistent behavior. It doesn't appear to be resolved at any point. However, it curiously works if called directly still. – cdeterman Jun 22 '18 at 13:46
  • I can not reproduce the error, in my computer it works fine. – Juan Antonio Roldán Díaz Jun 27 '18 at 11:08
  • I think that in your R session the methods are not registering the class, you can check if methods (class = "A") return [1] nrow typeof. – Juan Antonio Roldán Díaz Jun 27 '18 at 11:20
  • @JuanAntonioRoldánDíaz it seems to be the most reproducible if `bigmemory` is in the `Depends:` section of the `DESCRIPTION`. I have pushed this change if you could try once again. – cdeterman Jun 29 '18 at 14:39

2 Answers2

2

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"
Community
  • 1
  • 1
Alexis
  • 4,950
  • 1
  • 18
  • 37
  • Also note that it had nothing to do with another package defining the same generic. – Alexis Jul 02 '18 at 22:29
  • so would it be better to create a generic of the `base` functions or use the `get` approach you show here? I looking for the most generalizable approach. – cdeterman Jul 06 '18 at 15:19
  • You should read that documentation entry. Your best bet might be to make a new generic. – Alexis Jul 06 '18 at 17:22
0

The problem is that Depends is not always sure. Imports is safer than using Depends, yo can read more about this here.

In the global enviroment your package is using bigmemory::typeof, and inside nrow base::typeof.

> base::typeof(x)
[1] "S4"
> bigmemory::typeof(x)
[1] "typeof was called"
[1] "myClassA"

You can solve this by referring directly to the function with the name of the package you want to use.

  • Thanks but how would I call it from the example `s4test` package? Inside `nrow` I would want the `s4test::typeof` call which does not appear to work. It complains that it is not an exported object despite `exportMethods(typeof)` in the `NAMESPACE`. I also use `Depends` because my ultimate use case does require that use. At least at the moment, perhaps I can figure out a workaround otherwise. – cdeterman Jul 02 '18 at 15:35
  • But function `typeof` do not exist in `s4test`, if you want the output of the bigmemory package, you should use inside of nrow `bigmemory::typeof(x)` . – Juan Antonio Roldán Díaz Jul 03 '18 at 06:51