Search code examples
rr-s4r-s3

Method dispatch when mixing S3 and S4


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.


Solution

  • 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.