CC This work is licensed under the Creative Commons Attribution 4.0 International License. For questions please contact Michael Hahsler.

Introduction

S3 does not use formal class definitions. Only a class attribute is set.

o <- sample(LETTERS[c(1:4,6)], 10, replace = TRUE)
class(o) <- "grades"

o
##  [1] "C" "D" "A" "C" "C" "B" "B" "C" "A" "A"
## attr(,"class")
## [1] "grades"
str(o)
## Class 'grades'  chr [1:10] "C" "D" "A" "C" ...

S3 uses a dispatching mechanism for generic functions (see, e.g., ? print) and methods. Methods can be easily implemented for new classes. Note: the signature has to match the generic function definition!

Here is the generic for print

print
## function (x, ...) 
## UseMethod("print")
## <bytecode: 0xd78098>
## <environment: namespace:base>

Often there is a default method (the class name is appended using a period)

print.default
## function (x, digits = NULL, quote = TRUE, na.print = NULL, print.gap = NULL, 
##     right = FALSE, max = NULL, useSource = TRUE, ...) 
## {
##     noOpt <- missing(digits) && missing(quote) && missing(na.print) && 
##         missing(print.gap) && missing(right) && missing(max) && 
##         missing(useSource) && missing(...)
##     .Internal(print.default(x, digits, quote, na.print, print.gap, 
##         right, max, useSource, noOpt))
## }
## <bytecode: 0xb4ae68>
## <environment: namespace:base>

Note: To get the help page of the method, you often add the class name ? print.default

Write custom methods

Write a custom print method:

print.grades <- function(x, ...) {
  cat("Object of class", class(x), "with", length(x), "grades\n")
  print(unclass(x))
}

o
## Object of class grades with 10 grades
##  [1] "C" "D" "A" "C" "C" "B" "B" "C" "A" "A"

Write a custom mean method:

mean.grades <- function(x, ...) {
    x <- factor(x, levels = LETTERS[c(1:4,6)])
    x <- 5 - as.numeric(x)
    mean(x)
  }

mean(o)
## [1] 2.7

Defining new generic functions

R has many generic functions already defined. Sometimes it is convenient to define your own. Note: Make sure it does not exist!

exists("print_gpa")
## [1] FALSE
print_gpa <- function (x, ...) {
  UseMethod("print_gpa", x)
}

print_gpa
## function (x, ...) {
##   UseMethod("print_gpa", x)
## }
print_gpa.grades <- function(x, ...) {
    cat("Your grade point average is: ", mean(x), "\n")
}

print_gpa(o)
## Your grade point average is:  2.7

Inheritance

Inheritance can be implemented by using a vector of class names. Methods are chosen left to right.

class(o) <- c("grades", "letters")
str(o)
## Classes 'grades', 'letters'  chr [1:10] "C" "D" "A" "C" ...
is(o, "grades")
## [1] TRUE
is(o, "letters")
## [1] TRUE

Constructors

Best practice is to provide a constructor function.

grades <- function(x) {
  if(!is.character(x)) stop("Only characters allowed!")
  if(any(is.na(match(x, LETTERS[c(1:4,5)]))))
    stop("Illegal grade!")

  class(x) <- "grades"
  x
}


grades(c("A", "A", "A"))
## Object of class grades with 3 grades
## [1] "A" "A" "A"
try(
  grades(c(1, 2, 3))
)

try(
  grades(c("A", "I"))
)

More information on S3 can be found using ? S3 and at http://adv-r.had.co.nz/S3.html

Final note: R also has a formal class system called S4 (? S4) and reference classes (? ReferenceClasses)