Chapter 52 S3 Object System

What You’ll Learn:

  • S3 classes and objects
  • Generic functions and method dispatch
  • Inheritance
  • Common S3 errors

Key Errors Covered: 15+ S3 errors

Difficulty: ⭐⭐⭐ Advanced

52.1 Introduction

S3 is R’s simplest OOP system, used throughout base R and many packages.

# S3 objects are just lists with a class attribute
person <- list(name = "Alice", age = 30)
class(person) <- "person"

# Method dispatch based on class
class(mtcars)  # "data.frame"
#> [1] "data.frame"
class(lm(mpg ~ wt, mtcars))  # "lm"
#> [1] "lm"

52.2 Creating S3 Objects

💡 Key Insight: Constructor Pattern

# Constructor
new_person <- function(name, age) {
  structure(
    list(name = name, age = age),
    class = "person"
  )
}

# Validator
validate_person <- function(x) {
  if (!is.character(x$name)) stop("name must be character")
  if (!is.numeric(x$age) || x$age < 0) stop("age must be positive")
  x
}

# Helper (user-facing)
person <- function(name, age) {
  validate_person(new_person(name, age))
}

alice <- person("Alice", 30)
#> Error in match.arg(name, person_field_names): 'arg' should be one of "given", "family", "role", "email", "comment"

52.3 Generic Functions

# Create generic
greet <- function(x) {
  UseMethod("greet")
}

# Define methods
greet.person <- function(x) {
  paste("Hello", x$name)
}

greet.default <- function(x) {
  "Hello!"
}

# Test
greet(alice)  # Calls greet.person
#> [1] "Hello!"
greet(123)    # Calls greet.default
#> [1] "Hello!"

52.4 Common Errors

52.4.1 Error: no applicable method

# Forgot default method
process <- function(x) UseMethod("process")
process.person <- function(x) x$name

process(123)  # Error!
#> Error: No method for class numeric

Solution: Always provide default method

process.default <- function(x) {
  stop("No method for class ", class(x)[1], call. = FALSE)
}

52.5 Summary

Key Takeaways:

  1. S3 = simple OOP - Just lists with class attribute
  2. UseMethod() - Enables dispatch
  3. Generic.class - Method naming
  4. Always provide default - Avoid errors
  5. Validate inputs - Use constructor pattern

Quick Reference:

# Create class
new_class <- function(...) {
  structure(list(...), class = "myclass")
}

# Create generic
generic <- function(x) UseMethod("generic")

# Create method
generic.myclass <- function(x) {
  # Implementation
}

# Default
generic.default <- function(x) {
  stop("No method")
}