Yohan Chalabi
2009-Mar-18 13:47 UTC
[Rd] Why S4 methods of S3 'base' generics are not used in 'base' functions ?
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 -- PhD student Swiss Federal Institute of Technology Zurich www.ethz.ch
John Chambers
2009-Mar-18 18:13 UTC
[Rd] Why S4 methods of S3 'base' generics are not used in 'base' functions ?
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 > > >
Possibly Parallel Threads
- S4 Generics and NAMESPACE : justified warning ?
- Proper way to define cbind, rbind for s4 classes in package
- Suggestion for exception handling: More informative error message for "no applicable method..." (S3)
- Proper way to define cbind, rbind for s4 classes in package
- S4 classes and S3 generic functions