bobbel
bobbel

Reputation: 2031

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.

Upvotes: 0

Views: 436

Answers (2)

JDL
JDL

Reputation: 1654

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)."

Upvotes: 1

Russ Lenth
Russ Lenth

Reputation: 6830

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.

Upvotes: 1

Related Questions