r-oop

R Object-Oriented Programming

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "r-oop" with this command: npx skills add ab604/claude-code-r-skills/ab604-claude-code-r-skills-r-oop

R Object-Oriented Programming

S7, S3, S4, and vctrs: choosing the right OOP system for your needs

S7: Modern OOP for New Projects

  • S7 combines S3 simplicity with S4 structure

  • Formal class definitions with automatic validation

  • Compatible with existing S3 code

S7 class definition

Range <- new_class("Range", properties = list( start = class_double, end = class_double ), validator = function(self) { if (self@end < self@start) { "@end must be >= @start" } } )

Usage - constructor and property access

x <- Range(start = 1, end = 10) x@start # 1 x@end <- 20 # automatic validation

Methods

inside <- new_generic("inside", "x") method(inside, Range) <- function(x, y) { y >= x@start & y <= x@end }

OOP System Decision Matrix

S7 vs vctrs vs S3/S4 Decision Tree

Start here: What are you building?

  1. Vector-like objects (things that behave like atomic vectors)

Use vctrs when:

  • Need data frame integration (columns/rows)
  • Want type-stable vector operations
  • Building factor-like, date-like, or numeric-like classes
  • Need consistent coercion/casting behavior
  • Working with existing tidyverse infrastructure

Examples: custom date classes, units, categorical data

  1. General objects (complex data structures, not vector-like)

Use S7 when:

  • NEW projects that need formal classes
  • Want property validation and safe property access (@)
  • Need multiple dispatch (beyond S3's double dispatch)
  • Converting from S3 and want better structure
  • Building class hierarchies with inheritance
  • Want better error messages and discoverability

Use S3 when:

  • Simple classes with minimal structure needs
  • Maximum compatibility and minimal dependencies
  • Quick prototyping or internal classes
  • Contributing to existing S3-based ecosystems
  • Performance is absolutely critical (minimal overhead)

Use S4 when:

  • Working in Bioconductor ecosystem
  • Need complex multiple inheritance (S7 doesn't support this)
  • Existing S4 codebase that works well

Detailed S7 vs S3 Comparison

Feature S3 S7 When S7 wins

Class definition Informal (convention) Formal (new_class() ) Need guaranteed structure

Property access $ or attr() (unsafe) @ (safe, validated) Property validation matters

Validation Manual, inconsistent Built-in validators Data integrity important

Method discovery Hard to find methods Clear method printing Developer experience matters

Multiple dispatch Limited (base generics) Full multiple dispatch Complex method dispatch needed

Inheritance Informal, NextMethod()

Explicit super()

Predictable inheritance needed

Migration cost

Low (1-2 hours) Want better structure

Performance Fastest ~Same as S3 Performance difference negligible

Compatibility Full S3 Full S3 + S7 Need both old and new patterns

Practical Guidelines

Choose S7 when you have

Complex validation needs

Range <- new_class("Range", properties = list(start = class_double, end = class_double), validator = function(self) { if (self@end < self@start) "@end must be >= @start" } )

Multiple dispatch needs

method(generic, list(ClassA, ClassB)) <- function(x, y) ...

Class hierarchies with clear inheritance

Child <- new_class("Child", parent = Parent)

Choose vctrs when you need

Vector-like behavior in data frames

percent <- new_vctr(0.5, class = "percentage") data.frame(x = 1:3, pct = percent(c(0.1, 0.2, 0.3))) # works seamlessly

Type-stable operations

vec_c(percent(0.1), percent(0.2)) # predictable behavior vec_cast(0.5, percent()) # explicit, safe casting

Choose S3 when you have

Simple classes without complex needs

new_simple <- function(x) structure(x, class = "simple") print.simple <- function(x, ...) cat("Simple:", x)

Maximum performance needs (rare)

Existing S3 ecosystem contributions

S3 Patterns

Basic S3 Class

Constructor

new_person <- function(name, age) { stopifnot(is.character(name), length(name) == 1) stopifnot(is.numeric(age), length(age) == 1)

structure( list(name = name, age = age), class = "person" ) }

Print method

print.person <- function(x, ...) { cat("Person:", x$name, "(age", x$age, ")\n") invisible(x) }

Generic + method

greet <- function(x) UseMethod("greet") greet.person <- function(x) { cat("Hello, my name is", x$name, "\n") } greet.default <- function(x) { cat("Hello!\n") }

S3 Inheritance

Child class

new_employee <- function(name, age, company) { obj <- new_person(name, age) obj$company <- company class(obj) <- c("employee", class(obj)) obj }

Method with inheritance

print.employee <- function(x, ...) { NextMethod() # Call parent print method cat("Works at:", x$company, "\n") invisible(x) }

S7 Patterns

Basic S7 Class

library(S7)

Define class

Person <- new_class("Person", properties = list( name = class_character, age = class_numeric ), validator = function(self) { if (self@age < 0) { "@age must be non-negative" } } )

Create instance

bob <- Person(name = "Bob", age = 30) bob@name # "Bob" bob@age <- 31 # Validated assignment

S7 Methods

Define generic

greet <- new_generic("greet", "x")

Add method

method(greet, Person) <- function(x) { cat("Hello, my name is", x@name, "\n") }

Default method

method(greet, class_any) <- function(x) { cat("Hello!\n") }

S7 Inheritance

Employee <- new_class("Employee", parent = Person, properties = list( company = class_character ) )

Override method

method(greet, Employee) <- function(x) { super(x, Person)@greet() # Call parent method cat("I work at", x@company, "\n") }

S7 Multiple Dispatch

Generic with multiple dispatch

combine <- new_generic("combine", c("x", "y"))

Method for specific combination

method(combine, list(Person, Person)) <- function(x, y) { cat(x@name, "meets", y@name, "\n") }

method(combine, list(Person, class_character)) <- function(x, y) { cat(x@name, "receives message:", y, "\n") }

Migration Strategy

  • S3 -> S7: Usually 1-2 hours work, keeps full compatibility

  • S4 -> S7: More complex, evaluate if S4 features are actually needed

  • Base R -> vctrs: For vector-like classes, significant benefits

  • Combining approaches: S7 classes can use vctrs principles internally

Migration Example: S3 to S7

Original S3

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

Migrated S7

Person <- new_class("Person", properties = list( name = class_character, age = class_numeric ) )

S7 is backwards compatible with S3 generics

Existing S3 methods still work

When NOT to Use OOP

Sometimes simpler approaches are better:

Don't create a class for simple data

BAD

Point <- new_class("Point", properties = list(x = class_double, y = class_double))

GOOD - just use a named list or vector

point <- c(x = 1.5, y = 2.3)

Don't create classes for one-off operations

Use functions instead

distance <- function(p1, p2) { sqrt((p1["x"] - p2["x"])^2 + (p1["y"] - p2["y"])^2) }

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

r-performance

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

r-style-guide

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

tidyverse-patterns

No summary provided by upstream source.

Repository SourceNeeds Review