knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
The S7 package provides a new OOP system designed to be a successor to S3 and S4. It has been designed and implemented collaboratively by the RConsortium Object-Oriented Programming Working Group, which includes representatives from R-Core, BioConductor, RStudio/tidyverse, and the wider R community.
This vignette gives an overview of the most important parts of S7: classes and objects, generics and methods, and the basics of method dispatch and inheritance.
library(S7)
S7 classes have a formal definition that you create with new_class()
.
There are two arguments that you'll use with almost every class:
name
of the class, supplied in the first argument.properties
, the data associated with each instance of the class. The easiest way to define properties is to supply a named list where the values define the valid types of the property.The following code defines a simple dog
class with two properties: a character name
and a numeric age
.
Dog <- new_class("Dog", properties = list( name = class_character, age = class_numeric )) Dog
S7 provides a number of built-in definitions that allow you to refer to existing base types that are not S7 classes.
You can recognize these definitions because they all start with class_
.
Note that I've assigned the return value of new_class()
to an object with the same name as the class.
This is important!
That object represents the class and is what you use to construct instances of the class:
lola <- Dog(name = "Lola", age = 11) lola
Once you have an S7 object, you can get and set properties using @
:
lola@age <- 12 lola@age
S7 automatically validates the type of the property using the type supplied in new_class()
:
lola@age <- "twelve"
Given an object, you can retrieves its class S7_class()
:
S7_class(lola)
S7 objects also have an S3 class()
.
This is used for compatibility with existing S3 generics and you can learn more about it in vignette("compatibility")
.
class(lola)
If you want to learn more about the details of S7 classes and objects, including validation methods and more details of properties, please see vignette("classes-objects")
.
S7, like S3 and S4, is built around the idea of generic functions, or generics for short. A generic defines an interface, which uses a different implementation depending on the class of one or more arguments. The implementation for a specific class is called a method, and the generic finds that appropriate method by performing method dispatch.
Use new_generic()
to create a S7 generic.
In its simplest form, it only needs two arguments: the name of the generic (used in error messages) and the name of the argument used for method dispatch:
speak <- new_generic("speak", "x")
Like with new_class()
, you should always assign the result of new_generic()
to a variable with the same name as the first argument.
Once you have a generic, you can register methods for specific classes with method(generic, class) <- implementation
.
method(speak, Dog) <- function(x) { "Woof" }
Once the method is registered, the generic will use it when appropriate:
speak(lola)
Let's define another class, this one for cats, and define another method for speak()
:
Cat <- new_class("Cat", properties = list( name = class_character, age = class_double )) method(speak, Cat) <- function(x) { "Meow" } fluffy <- Cat(name = "Fluffy", age = 5) speak(fluffy)
You get an error if you call the generic with a class that doesn't have a method:
speak(1)
The cat
and dog
classes share the same properties, so we could use a common parent class to extract out the duplicated specification.
We first define the parent class:
Pet <- new_class("Pet", properties = list( name = class_character, age = class_numeric ) )
Then use the parent
argument to new_class:
Cat <- new_class("Cat", parent = Pet) Dog <- new_class("Dog", parent = Pet) Cat Dog
Because we have created new classes, we need to recreate the existing lola
and fluffy
objects:
lola <- Dog(name = "Lola", age = 11) fluffy <- Cat(name = "Fluffy", age = 5)
Method dispatch takes advantage of the hierarchy of parent classes: if a method is not defined for a class, it will try the method for the parent class, and so on until it finds a method or gives up with an error. This inheritance is a powerful mechanism for sharing code across classes.
describe <- new_generic("describe", "x") method(describe, Pet) <- function(x) { paste0(x@name, " is ", x@age, " years old") } describe(lola) describe(fluffy) method(describe, Dog) <- function(x) { paste0(x@name, " is a ", x@age, " year old dog") } describe(lola) describe(fluffy)
You can define a fallback method for any S7 object by registering a method for S7_object
:
method(describe, S7_object) <- function(x) { "An S7 object" } Cocktail <- new_class("Cocktail", properties = list( ingredients = class_character ) ) martini <- Cocktail(ingredients = c("gin", "vermouth")) describe(martini)
Printing a generic will show you which methods are currently defined:
describe
And you can use method()
to retrieve the implementation of one of those methods:
method(describe, Pet)
Learn more about method dispatch in vignette("generics-methods")
.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.