From jmc at r-project.org  Wed Mar 18 19:13:13 2009
From: jmc at r-project.org (John Chambers)
Date: Wed, 18 Mar 2009 11:13:13 -0700
Subject: [Rd] Why S4 methods of S3 'base' generics are not used in
 'base' functions ?
In-Reply-To: <20090318144703.54e02ec1@mimi>
References: <20090318144703.54e02ec1@mimi>
Message-ID: <49C139B9.6040302@r-project.org>

The short answer is because S3 method dispatch knows nothing about S4 
methods and never has (but maybe should).  You select S4 methods by 
creating and calling an S4 generic outside of base, and base functions 
don't call it.

Details:

Your assertion is not entirely correct.  As always, you need to look at 
the individual function.

There are two different situations.  While, e.g., both `+` and as.matrix 
can have S3 or S4 methods, the mechanism is different.

R(r48116)> `+`
function (e1, e2)  .Primitive("+")
R(r48116)> as.matrix
function (x, ...)
UseMethod("as.matrix")
<environment: namespace:base>

In either case, S4 methods will be dispatched _if_ the package involved 
has correctly defined them, but the mechanism is different, and in one 
case requires the call to come from somewhere that sees the S4 methods.

For primitives, no explicit S4 generic function is defined.  Instead, 
the underlying C  code dispatches S4 methods once a package has "turned 
on" methods for that function.

For S3 generics, such as as.matrix, the setMethod() in the package 
creates an S4 generic.  This function calls for the S4 method dispatch. 
The object base::as.matrix is still an S3 generic. While it might be 
good for UseMethod() to recognize that S4 methods exist, it doesn't and 
won't for 2.9.0.

The problem with the call to scale() is then that scale.default calls 
as.matrix() _from the base namespace_  (as it should) and the resulting 
UseMethod() doesn't dispatch the S4 method (where I tend to agree with 
you that it sensibly should, given that x is an S4 object). To see the 
behavior, trace the two versions:

R(r48116)> find("as.matrix")
[1] ".GlobalEnv"   "package:base"
R(r48116)> trace(as.matrix) ## in global env.
R(r48116)> y <- scale(x) ## no trace() output
R(r48116)> trace(base::as.matrix) ## in base
R(r48116)> y <- scale(x)
trace: as.matrix(x)
R(r48116)>

Until UseMethod() does S4 dispatch, you will need to supply base 
functions with objects they understand, as in your second call to scale().

Without taking back my diatribe on S3 methods for S4 classes, I believe 
they now do work again for R 2.9.0, so you can decide whether you should 
revise and how.

John

Yohan Chalabi wrote:
> Dear list,
>
> It seems that S4 methods defined for an S3 'base' generic
> are not used in 'base' functions.
>
> This can be problematic when 'base' functions start with 
> something like 'as.matrix'.
>
>
> ### START R code
>
> setClass("classA", contains = "matrix",
>          representation(realData = "numeric"))
>
> setMethod("as.matrix", "classA", function(x) callGeneric(x at realData))
>
> x <- new("classA", diag(1:4), realData = 1:4)
>
> as.matrix(x)
>
> ## # as intended
> ##      [,1]
> ## [1,]    1
> ## [2,]    2
> ## [3,]    3
> ## [4,]    4
>
> # but as.matrix in 'base' functions dispatches to the default S3
> # method rather than to the S4 method defined above.
> scale(x)
> scale(as.matrix(x))
>
> # Note that S4 methods are well dispatched for functions which are
> # not S3 generics.
> setMethod("dimnames", "classA",
>           function(x) list(NULL, as.character(x at realData)))
> dimnames(x)
>
> solve(x) # here row names are properly assigned thanks to the 'dimnames'
>          # method defined above.
>
> ### END R code
>
> What is your recommended solution to make S4 methods of S3 'base'
> generics work in 'base' functions?
>
> A solution could be to overwrite 'as.matrix' in '.Load' and force it to
> use the S4 method with S4 objects. But doing so looks to me rather
> dangerous because it would lead to conflicts between packages.
>
> Another solution could be to define S3 methods. But, as it has been
> already explained on the list, it is a design error.
>
> Thanks in advance for any suggestion!
>
> Best regards,
> Yohan
>
>
>



[Rd] Why S4 methods of S3 'base' generics are not used in 'base' functions ?
John Chambers