Reputation: 2031
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
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
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