setOldClass             package: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)

