R: Register Old-Style (S3) Classes and Inheritance

setOldClass {methods} | R Documentation

## Register Old-Style (S3) Classes and Inheritance

### Description

Register an old-style (a.k.a. ‘S3’) class as a formally defined class. The `Classes` argument is the character vector used as the `class` attribute; in particular, if there is more than one string, old-style class inheritance is mimicked. Registering via `setOldClass` allows S3 classes to appear in method signatures, as a slot in an S4 class, or as a superclass of an S4 class.

### Usage

    setOldClass(Classes, prototype, where, test = FALSE, S4Class)

### Arguments

`Classes` | A character vector, giving the names for old-style classes, as they would appear on the right side of an assignment of the `class` attribute in S3 computations.
`prototype` | An optional object to use as the prototype. This should be provided as the default S3 object for the class. If omitted, the S4 class created to register the S3 class is `VIRTUAL`. See the details.
`where` | Where to store the class definitions, the global or top-level environment by default. (When either function is called in the source for a package, the class definitions will be included in the package's environment by default.)
`test` | flag, if `TRUE`, arrange to test inheritance explicitly for each object, needed if the S3 class can have a different set of class strings, with the same first string. This is a different mechanism in implementation and should be specified separately for each pair of classes that have an optional inheritance. See the details below.
`S4Class` | optionally, the class definition or the class name of an S4 class. The new class will have all the slots and other properties of this class, plus its S3 inheritance as defined by the `Classses` argument. Arguments `prototype` and `test` must not be supplied in this case. See the section on “S3 classes with known attributes” below.

### Details

Each of the names will be defined as an S4 class, extending the remaining classes in `Classes`, and the class `oldClass`, which is the ‘root’ of all old-style classes. S3 classes have no formal definition, and therefore no formally defined slots. If a `prototype` argument is supplied in the call to `setOldClass()`, objects from the class can be generated, by a call to `new`; however, this usually not as relevant as generating objects from subclasses (see the section on extending S3 classes below). If a prototype is not provided, the class will be created as a virtual S4 class. The main disadvantage is that the prototype object in an S4 class that uses this class as a slot will have a `NULL` object in that slot, which can sometimes lead to confusion.

Beginning with version 2.8.0 of R, support is provided for using a (registered) S3 class as a super class of a new S4 class. See the section on extending S3 classes below, and the examples.

See Methods for the details of method dispatch and inheritance.

Some S3 classes cannot be represented as an ordinary combination of S4 classes and superclasses, because objects from the S3 class can have a variable set of strings in the class. It is still possible to register such classes as S4 classes, but now the inheritance has to be verified for each object, and you must call `setOldClass` with argument `test=TRUE` once for each superclass.

For example, ordered factors always have the S3 class `c("ordered", "factor")`. This is proper behavior, and maps simply into two S4 classes, with `"ordered"` extending `"factor"`.

But objects whose class attribute has `"POSIXt"` as the first string may have either (or neither) of `"POSIXct"` or `"POSIXlt"` as the second string. This behavior can be mapped into S4 classes but now to evaluate `is(x, "POSIXlt")`, for example, requires checking the S3 class attribute on each object. Supplying the `test=TRUE` argument to `setOldClass` causes an explicit test to be included in the class definitions. It's never wrong to have this test, but since it adds significant overhead to methods defined for the inherited classes, you should only supply this argument if it's known that object-specific tests are needed.

The list `.OldClassesList` contains the old-style classes that are defined by the methods package. Each element of the list is an old-style list, with multiple character strings if inheritance is included. Each element of the list was passed to `setOldClass` when creating the methods package; therefore, these classes can be used in `setMethod` calls, with the inheritance as implied by the list.

### Extending S3 classes

A call to `setOldClass` creates formal classes corresponding to S3 classes, allows these to be used as slots in other classes or in a signature in `setMethod`, and mimics the S3 inheritance.

In documentation for the initial implementation of S4 classes in R, users were warned against defining S4 classes that contained S3 classes, even if those had been registered. The warning was based mainly on two points. 1: The S3 behavior of the objects would fail because the S3 class would not be visible, for example, when S3 methods are dispatched. 2: Because S3 classes have no formal definition, nothing can be asserted in general about the S3 part of an object from such a class. (The warning was repeated as recently as the first reference below.)

Nevertheless, defining S4 classes to contain an S3 class and extend its behavior is attractive in many applications. The alternative is to be stuck with S3 programming, without the flexibility and security of formal class and method definitions.

Beginning with version 2.8.0, R provides support for extending registered S3 classes; that is, for new classes defined by a call to `setClass` in which the `contains=` argument includes an S3 class. See the examples below. The support is aimed primarily at providing the S3 class information for all classes that extend class oldClass, in particular by ensuring that all objects from such classes contain the S3 class in a special slot.

There are three different ways to indicate an extension to an existing S3 class: `setOldClass()`, `setClass()` and `setIs()`. In most cases, calling `setOldClass` is the best approach, but the alternatives may be preferred in the special circumstances described below.

Suppose `"A"` is any class extending `"oldClass"`. then

`setOldClass(c("B", "A"))`

creates a new class `"B"` whose S3 class concatenates `"B"` with `S3Class("A")`. The new class is a virtual class. If `"A"` was defined with known attribute/slots, then `"B"` has these slots also; therefore, you must believe that the corresponding S3 objects from class `"B"` do indeed have the claimed attributes. Notice that you can supply an S4 definition for the new class to specify additional attributes (as described in the next section.) The first alternative call produces a non-virtual class.

`setClass("B", contains = "A")`

This creates a non-virtual class with the same slots and superclasses as class `"A"`. However, class `"B"` is not included in the S3 class slot of the new class, unless you provide it explicitly in the prototype.

`setClass("B"); setIs("B", "A", .....)`

This creates a virtual class that extends `"A"`, but does not contain the slots of `"A"`. The additional arguments to `setIs` should provide a coerce and replacement method. In order for the new class to inherit S3 methods, the coerce method must ensure that the class `"A"` object produced has a suitable S3 class. The only likely reason to prefer this third approach is that class `"B"` is not consistent with known attributes in class `"A"`.

### S3 Classes with known attributes

A further specification of an S3 class can be made if the class is guaranteed to have some attributes of known class (where as with slots, “known” means that the attribute is an object of a specified class, or a subclass of that class).

In this case, the call to `setOldClass()` can supply an S4 class definition representing the known structure. Since S4 slots are implemented as attributes (largely for just this reason), the know attributes can be specified in the representation of the S4 class. The usual technique will be to create an S4 class with the desired structure, and then supply the class name or definition as the argument `S4Class` to `setOldClass()`.

See the definition of class `"ts"` in the examples below. The call to `setClass` to create the S4 class can use the same class name, as here, so long as the class definition is not sealed. In the example, we define `"ts"` as a vector structure with a numeric slot for `"tsp"`. The validity of this definition relies on an assertion that all the S3 code for this class is consistent with that definition; specifically, that all `"ts"` objects will behave as vector structures and will have a numeric `"tsp"` attribute. We believe this to be true of all the base code in R, but as always with S3 classes, no guarantee is possible.

The S4 class definition can have virtual superclasses (as in the `"ts"` case) if the S3 class is asserted to behave consistently with these (in the example, time-series objects are asserted to be consistent with the structure class).

For another example, look at the S4 class definition for `"data.frame"`.

Be warned that failures of the S3 class to live up to its asserted behavior will usually go uncorrected, since S3 classes inherently have no definition, and the resulting invalid S4 objects can cause all sorts of grief. Many S3 classes are not candidates for known slots, either because the presence or class of the attributes are not guaranteed (e.g., `dimnames` in arrays, although these are not even S3 classes), or because the class uses named components of a list rather than attributes (e.g., `"lm"`). An attribute that is sometimes missing cannot be represented as a slot, not even by pretending that it is present with class `"NULL"`, because attributes unlike slots can not have value `NULL`.

One irregularity that is usually tolerated, however, is to optionally add other attributes to those guaranteed to exist (for example, `"terms"` in `"data.frame"` objects returned by `model.frame`). As of version 2.8.0, validity checks by `validObject` ignore extra attributes; even if this check is tightened in the future, classes extending S3 classes would likely be exempted because extra attributes are so common.

### References

Chambers, John M. (2008) Software for Data Analysis: Programming with R Springer. (For the R version: see section 10.6 for method selection and section 13.4 for generic functions).

Chambers, John M. (1998) Programming with Data Springer (For the original S4 version.)

### See Also

`setClass`, `setMethod`

### Examples

    require(stats)
    setOldClass(c("mlm", "lm"))
    setGeneric("dfResidual", function(model)standardGeneric("dfResidual"))
    setMethod("dfResidual", "lm", function(model)model$df.residual)

    ## dfResidual will work on mlm objects as well as lm objects
    myData <- data.frame(time = 1:10, y = (1:10)^.5)
    myLm <- lm(cbind(y, y^3)  ~ time, myData)

    ## two examples extending S3 class "lm", class "xlm"  directly and "ylm" indirectly
    setClass("xlm", representation(eps = "numeric"), contains = "lm")
    setClass("ylm", representation(header = "character"), contains = "xlm")
    ym1 = new("ylm", myLm, header = "Example", eps = 0.)
    ## for more examples, see ?S3Class.

    ## Examples of S3 classes with guaranteed attributes
    ## an S3 class "stamped" with a vector and  a "date" attribute
    ## Here is a generator function and an S3 print method.
    ## NOTE:  it's essential that the generator checks the attribute classes
    stamped <- function(x, date = Sys.time()) {
        if(!inherits(date, "POSIXt"))
          stop("bad date argument")
        if(!is.vector(x))
          stop("x must be a vector")
        attr(x, "date") <- date
        class(x) <- "stamped"
        x
    }

    print.stamped <- function(x, ...) {
        print(as.vector(x))
        cat("Date: ",  format(attr(x,"date")), "\n")
    }

    ## Now, an S4 class with the same structure:
    setClass("stamped4", contains = "vector", representation(date = "POSIXt"))

    ## We can use the S4 class to register "stamped", with its attributes:
    setOldClass("stamped", S4Class = "stamped4")
    selectMethod("show", "stamped")
    ## and then remove "stamped4" to clean up
    removeClass("stamped4")

    someLetters <- stamped(sample(letters, 10),  ISOdatetime(2008, 10, 15, 12, 0, 0))

    st <- new("stamped", someLetters)
    st  # show() method prints the object's class, then calls the S3 print method.

    stopifnot(identical(S3Part(st, TRUE), someLetters))

    # creating the S4 object directly from its data part and slots
    new("stamped", 1:10, date = ISOdatetime(1976, 5, 5, 15, 10, 0))

    ## Not run:
    ## The code in R that defines "ts" as an S4 class
    setClass("ts", contains = "structure",
          representation(tsp = "numeric"),
          prototype(NA, tsp = rep(1,3))) # prototype to be a legal S3 time-series
    ## and now registers it as an S3 class
        setOldClass("ts", S4Class = "ts", where = envir)

    ## End(Not run)

[Package methods version 2.8.1 Index]