S4 and Reference Classes

R · Expert · 16 min

Bioconductor, Matrix and many large packages are built on S4, and RC/R6 are used for stateful objects.

S4 (the methods package) is a formal object system. setClass("Name", representation) declares a class with typed slots, new("Name", ...) creates instances, and obj@slot accesses slots (slot() and @<- modify them). A validity function enforces invariants and validObject rechecks them. Classes support inheritance with contains =.

Behaviour lives in generics and methods: setGeneric("area", function(shape) standardGeneric("area")) creates a generic and setMethod("area", "Circle", function(shape) ...) registers an implementation. S4 supports multiple dispatch, choosing a method based on the classes of several arguments, which suits operations like matrix arithmetic. show is S4's print. isVirtualClass, existsMethod and showMethods help explore.

S4 objects still have copy-on-modify value semantics. Reference Classes (setRefClass) are built on S4 plus environments: objects are mutable, methods can change fields with <<-, and copies must be made explicitly with $copy(). They feel like classes in Java or Python. The R6 package offers the same style with less overhead and is the popular choice outside base R (Shiny uses it).

Choose S3 for most work, S4 when you need formal definitions, validity and multiple dispatch (and in Bioconductor), and R6/RC when objects genuinely represent mutable state such as a connection or a simulation.

Example

setClass("Shape", representation("VIRTUAL"))
setClass("Circle", contains = "Shape", representation(r = "numeric"),
         validity = function(object) if (object@r < 0) "r must be >= 0" else TRUE)
setClass("Rect", contains = "Shape", representation(w = "numeric", h = "numeric"))

setGeneric("area", function(shape) standardGeneric("area"))  #> [1] "area"
setMethod("area", "Circle", function(shape) pi * shape@r^2)
setMethod("area", "Rect", function(shape) shape@w * shape@h)

shapes <- list(new("Circle", r = 1), new("Rect", w = 2, h = 3))
print(round(sapply(shapes, area), 2))      #> [1] 3.14 6.00
print(is(shapes[[1]], "Shape"))            #> [1] TRUE
print(tryCatch(new("Circle", r = -1), error = \(e) "invalid"))  #> [1] "invalid"

Account <- setRefClass("Account", fields = list(balance = "numeric"),
  methods = list(deposit = function(x) { balance <<- balance + x; invisible(.self) }))
a <- Account$new(balance = 10)
b <- a                                     # same object, not a copy
b$deposit(5)
print(a$balance)                           #> [1] 15

Remember

  • S4: setClass with typed slots, @ access and validity checks
  • setGeneric + setMethod, with multiple dispatch
  • Reference Classes and R6 are mutable with reference semantics
  • Default to S3; use S4 or R6 when you need what they add

Watch out for

  • Assuming b <- a copies a Reference Class object; use a$copy().

Check yourself

  1. What does S4 offer that S3 lacks?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: stat.ethz.ch/R-manual/R-devel/library/methods/html/setClass.html

Builds on

Unlocks

Nothing builds on this yet.