Question

I want to subclass an S4 class and add a special method to that subclass. The method should work only for the subclass, it does not make sense for any other class in my application.

    setClass("MySpecies", contains="Species", ##Species is an S4 class
         representation(x="numeric"))

setMethod("initialize", "MySpecies", function(.Object, x, ...){
  .Object@x <- x
  args <- list(...)
  for(i in seq_len(length(args))){
    attr(.Object, names(args)[i]) <- args[[i]]
  }
  .Object
})

##CalcMatrix <- function(.Object, y){
##  x <- .Object@x
##  matrix(x, x*2, y*3)
##}

setGeneric("CalcMatrix", function(object, y){standardGeneric("CalcMatrix")})
setMethod("CalcMatrix", "MySpecies",function(object, y){
  x <- object@x
  matrix(x, x*2, y*3)
})

With the setGeneric it works, but do I really have to define a generic function although it will be used only with this object? The commented out part works, but then there is no check if the function is called with the right arguments. What is the correct way to do this? Thanks in advance.

Was it helpful?

Solution

You're wanting to use method dispatch, and every method needs to be associated with a generic, so yes, setGeneric is required.

And for a little unasked-for advice... It's a bit weird to use a formal class system (presumably because the well-defined classes help in writing more complicated programs) and then to subvert the structure by adding arbitrary attributes; these should really be additional, well-defined slots in your class.

Let's make your example reproducible by defining Species

setClass("Species", representation(y="numeric"))
setClass("MySpecies", contains="Species", ##Species is an S4 class
         representation(x="numeric"))

An implicit requirement for S4 classes is that new("MySpecies") works; your initialize method fails this test (because x does not have a default value). In addition, it's common practice to expect that initializing MySpecies calls the initialize methods for the classes it contains. One could write

setMethod("initialize", "MySpecies", function(.Object, ..., x=numeric()) {
    callNextMethod(.Object, x=x, ...)
})

Note callNextMethod, so that the base class gets initialized properly. Using ... and passing it to callNextMethod means that slots that might be defined in Species would also be initialized correctly. Also, x needs to be after ..., because initialize is defined to take unnamed arguments that represent contained classes -- new("MySpecies", new("Species")) is required to work, even if it is a way of constructing arguments that you do not use directly. The initialize method above doesn't actually do anything more than the default initialize method, so in reality (and this is often the case) it makes sense not to write an initialize method at all.

And then in more recent R, setClass returns a default constructor so

MySpecies <- setClass("MySpecies", contains="Species", ##Species is an S4 class
                      representation(x="numeric"))

and then

> MySpecies(x=1:5, y=5:1)
An object of class "MySpecies"
Slot "x":
[1] 1 2 3 4 5

Slot "y":
[1] 5 4 3 2 1
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top