print, summary, plot and predict all work through S3, and most packages define S3 classes.
S3 is R's original and most widely used object system. An S3 object is an ordinary object (usually a list) with a class attribute, a character vector of class names from most to least specific.
A generic function calls UseMethod("name"), which dispatches to the method named name.class for the first class that has one, falling back to name.default. So print(x) on an object of class "temperature" calls print.temperature(x) if it exists. Inside a method, NextMethod() calls the next method in the class vector, the S3 version of super.
To create a class, write a constructor that builds the underlying object and sets the class with structure(list(...), class = "myclass"), plus a validator for invariants. Then add methods for the generics you need: print, format, summary, arithmetic via the Ops group generic, or your own generics.
inherits(x, "myclass") tests class membership; unclass(x) strips the class to show the raw structure. methods(class = "lm") lists methods for a class.
S3 has no formal definitions or checks: anything can claim any class. That makes it flexible and fast to use, but discipline (constructors, validators) matters. Data frames, factors, Dates and lm fits are all S3 objects.
Example
temperature <- function(celsius) {
stopifnot(is.numeric(celsius))
structure(list(c = celsius), class = c("temperature", "measure"))
}
print.temperature <- function(x, ...) {
cat(sprintf("%.1f°C\n", x$c))
invisible(x)
}
to_f <- function(x, ...) UseMethod("to_f")
to_f.temperature <- function(x, ...) x$c * 9 / 5 + 32
to_f.default <- function(x, ...) stop("don't know how to convert ", class(x)[1])
summary.measure <- function(object, ...) cat("a measure of class", class(object)[1], "\n")
t1 <- temperature(21.5)
print(t1) #> 21.5°C
t1 # auto-print also dispatches
print(to_f(t1)) #> [1] 70.7
summary(t1) #> a measure of class temperature
print(inherits(t1, "measure")) #> [1] TRUE
print(tryCatch(to_f(1), error = conditionMessage))
#> [1] "don't know how to convert numeric"Remember
- An S3 object is a base object with a class attribute
UseMethoddispatches togeneric.class, thengeneric.default- Write a constructor; S3 itself enforces nothing
NextMethod()delegates to the next class
Watch out for
- Naming ordinary functions with dots like
print.databy accident, which turns them into methods.