Basic use of S4 Methods and Classes

Description

The majority of applications using methods and classes will be in R packages implementing new computations for an application, using new classes of objects that represent the data and results. Computations will be implemented using methods that implement functional computations when one or more of the arguments is an object from these classes.

Calls to the functions setClass() define the new classes; calls to setMethod define the methods. These, along with ordinary R computations, are sufficient to get started for most applications.

Classes are defined in terms of the data in them and what other classes of data they inherit from. Section ‘Defining Classes’ outlines the basic design of new classes.

Methods are R functions, often implementing basic computations as they apply to the new classes of objects. Section ‘Defining Methods’ discusses basic requirements and special tools for defining methods.

The classes discussed here are the original functional classes. R also supports formal classes and methods similar to those in other languages such as Python, in which methods are part of class definitions and invoked on an object. These are more appropriate when computations expect references to objects that are persistent, making changes to the object over time. See ReferenceClasses and Chapter 9 of the reference for the choice between these and S4 classes.

Defining Classes

All objects in R belong to a class; ordinary vectors and other basic objects are built-in (builtin-class). A new class is defined in terms of the named slots that is has and/or in terms of existing classes that it inherits from, or contains (discussed in ‘Class Inheritance’ below). A call to setClass() names a new class and uses the corresponding arguments to define it.

For example, suppose we want a class of objects to represent a collection of positions, perhaps from GPS readings. A natural way to think of these in R would have vectors of numeric values for latitude, longitude and altitude. A class with three corresponding slots could be defined by:

Pos <- setClass("Pos", slots = c(latitude = "numeric", longitude = "numeric", altitude = "numeric"))

The value returned is a function, typically assigned as here with the name of the class. Calling this function returns an object from the class; its arguments are named with the slot names. If a function in the class had read the corresponding data, perhaps from a CSV file or from a data base, it could return an object from the class by:

Pos(latitude = x, longitude = y, altitude = z)

The slots are accessed by the @ operator; for example, if g is an object from the class, g@latitude.

In addition to returning a generator function the call to setClass() assigns a definition of the class in a special metadata object in the package's namespace. When the package is loaded into an R session, the class definition is added to a table of known classes.

To make the class and the generating function publicly available, the package should include POS in exportClasses() and export() directives in its NAMESPACE file:

exportClasses(Pos); export(Pos)

Defining Methods

Defining methods for an R function makes that function generic. Instead of a call to the function always being carried out by the same method, there will be several alternatives. These are selected by matching the classes of the arguments in the call to a table in the generic function, indexed by classes for one or more formal arguments to the function, known as the signatures for the methods.

A method definition then specifies three things: the name of the function, the signature and the method definition itself. The definition must be a function with the same formal arguments as the generic.

For example, a method to make a plot of an object from class "Pos" could be defined by:

setMethod("plot", c("Pos", "missing"), function(x, y, ...) { plotPos(x, y) })

This method will match a call to plot() if the first argument is from class "Pos" or a subclass of that. The second argument must be missing; only a missing argument matches that class in the signature. Any object will match class "ANY" in the corresponding position of the signature.

Class Inheritance

A class may inherit all the slots and methods of one or more existing classes by specifying the names of the inherited classes in the contains = argument to setClass().

To define a class that extends class "Pos" to a class "GPS" with a slot for the observation times:

GPS <- setClass("GPS", slots = c(time = "POSIXt"), contains = "Pos")

The inherited classes may be S4 classes, S3 classes or basic data types. S3 classes need to be identified as such by a call to setOldClass(); most S3 classes in the base package and many in the other built-in packages are already declared, as is "POSIXt". If it had not been, the application package should contain:

setOldClass("POSIXt")

Inheriting from one of the R types is special. Objects from the new class will have the same type. A class Currency that contains numeric data plus a slot "unit" would be created by

Currency <- setClass("Currency", slots = c(unit = "character"), contains = "numeric")

Objects created from this class will have type "numeric" and inherit all the builtin arithmetic and other computations for that type. Classes can only inherit from at most one such type; if the class does not inherit from a type, objects from the class will have type "S4".

References

Chambers, John M. (2016) Extending R, Chapman & Hall. (Chapters 9 and 10.)

Want to suggest features or report bugs for rdrr.io? Use the GitHub issue tracker.