0

I'd like to understand the steps R goes through to find the appropriate function when mixing S3 and S4. Here's an example:

set.seed(1)
d <- data.frame(a=rep(c('a', 'b'), each=15),
                b=rep(c('x', 'y', 'z'), times=5),
                y=rnorm(30))

m <- lme4::lmer(y ~ b + (1|a), data=d)
l <- lsmeans::lsmeans(m, 'b')
multcomp::cld(l)

I don't fully understand what happens when the final line gets executed.

multcomp::cld prints UseMethod("cld"), so S3 method dispatch.

isS4(l) shows that l is an S4 class object.

It seems that, despite calling an S3 generic, the S3 dispatch system is completely ignored. Creating a function print.lsmobj <- function(obj) print('S3') (since class(l) is lsmobj) and running cld(l) does not print "S3".

showMethods(lsmobj) or showMethods(ref.grid) (the super class), do not list anything that resembles a cld function.

Using debugonce(multcomp::cld) shows that the function that is called eventually is cld.ref.grid from lsmeans.

I was wondering, however, how to realise that cld.ref.grid will eventually be called without any "tricks" like debugonce. That is, what are the steps R performs to get to cld.ref.grid.

bobbel
  • 1,983
  • 6
  • 21
  • You really should upgrade lsmeans. You have a very old version. The lsmobj class went away a long time ago. – Russ Lenth Nov 04 '19 at 13:40
  • Ah I am aware of that. The question is not really linked to the package specifically, but to R's method dispatch system. It's just that I had to debug some code where lsmeans is still used, and used it therefore as example. – bobbel Nov 04 '19 at 13:46
  • I wonder if you load the multcomp library first, if cld would show up in showMethods. That is, if the generic is not visible, why should methods be shown. – Russ Lenth Nov 04 '19 at 13:58
  • OP says '... cld(l) does not print "S3" ' but I think this is a typo, since you defined a `print` method, not a `cld` method. – Russ Lenth Nov 04 '19 at 16:17
  • @rvl, you're right. Wasn't thinking there. – bobbel Nov 05 '19 at 08:04

2 Answers2

1

In order for S3 methods to be registered, the generic has to be available. Here, I write a simple foo method for merMod objects:

> library(lme4)
> foo.merMod = function(object, ...) { "foo" }

> showMethods(class = "merMod")

Function ".DollarNames":
 <not an S4 generic function>

Function "complete":
 <not an S4 generic function>

Function "formals<-":
 <not an S4 generic function>

Function "functions":
 <not an S4 generic function>
Function: getL (package lme4)
x="merMod"

Function "prompt":
 <not an S4 generic function>
Function: show (package methods)
object="merMod"

> methods(class = "merMod")
 [1] anova          as.function    coef           confint        cooks.distance
 [6] deviance       df.residual    drop1          extractAIC     family        
[11] fitted         fixef          formula        getL           getME         
[16] hatvalues      influence      isGLMM         isLMM          isNLMM        
[21] isREML         logLik         model.frame    model.matrix   ngrps         
[26] nobs           plot           predict        print          profile       
[31] ranef          refit          refitML        rePCA          residuals     
[36] rstudent       show           sigma          simulate       summary       
[41] terms          update         VarCorr        vcov           weights              

Neither list includes foo. But if we define the generic, then it shows up in methods() results:

> foo = function(object, ...) UseMethod("foo")
> methods(class = "merMod")
 [1] anova          as.function    coef           confint        cooks.distance
 [6] deviance       df.residual    drop1          extractAIC     family        
[11] fitted         fixef          foo            formula        getL          
[16] getME          hatvalues      influence      isGLMM         isLMM         
[21] isNLMM         isREML         logLik         model.frame    model.matrix  
[26] ngrps          nobs           plot           predict        print         
[31] profile        ranef          refit          refitML        rePCA         
[36] residuals      rstudent       show           sigma          simulate      
[41] summary        terms          update         VarCorr        vcov          
[46] weights       

Now it includes foo

Similarly, in your example, methods() will reveal the existence of cld if you do library(multcomp), because that is where the generic for cld sits.

Russ Lenth
  • 5,922
  • 2
  • 13
  • 21
  • This definitely helps, but I'd still like to know how R determines to call the function `cld.ref.grid` when executing `multcomp::cld(l)`. – bobbel Nov 04 '19 at 16:01
  • 1
    Because `multcomp::cld` calls `UseMethod`, which in turn looks for possible methods for `l`, which is of class `c("lsmobj", "ref.grid")`; and finds there is indeed one registered for the `ref.grid` class but not one for `lsmobj`. So it uses the one for `ref.grid`. Method registration is done in the `NAMESPACE` file for **lsmeans**, which includes the line `S3method("cld", "ref.grid")` – Russ Lenth Nov 04 '19 at 16:12
1

The older R documentation (pre-2016) used to contain more details than the current documentation but roughly speaking, the process is as follows in descending order of priority:

1) if the function is a standard S4 generic and any of the arguments in the signature are S4 (according to isS4), then the best S4 method is chosen according to the usual rules.

2) if the function is a nonstandard S4 generic then its body is executed, which at some point then calls S4 dispatch itself.

3) if the function is a S3 generic function then S3 dispatch takes place on the first argument (except for internal generic binary operators).

4) if the function isn't a generic at all, then it is evaluated in the usual way with lazy evaluation for all its arguments.

Note that from the help page from setGeneric:

"Functions that dispatch S3 methods by calling UseMethod are ordinary functions, not objects from the "genericFunction" class. They are made generic like any other function, but some special considerations apply to ensure that S4 and S3 method dispatch is consistent (see Methods_for_S3)."

JDL
  • 1,496
  • 10
  • 18