The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

S7 basics

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)

Classes and objects

S7 classes have a formal definition that you create with new_class(). There are two arguments that you’ll use with almost every class:

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
#> <Dog> class
#> @ parent     : <S7_object>
#> @ constructor: function(name, age) {...}
#> @ validator  : <NULL>
#> @ properties :
#>  $ name: <character>          
#>  $ age : <integer> or <double>

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
#> <Dog>
#>  @ name: chr "Lola"
#>  @ age : num 11

Once you have an S7 object, you can get and set properties using @:

lola@age <- 12
lola@age
#> [1] 12

S7 automatically validates the type of the property using the type supplied in new_class():

lola@age <- "twelve"
#> Error: <Dog>@age must be <integer> or <double>, not <character>

Given an object, you can retrieves its class S7_class():

S7_class(lola)
#> <Dog> class
#> @ parent     : <S7_object>
#> @ constructor: function(name, age) {...}
#> @ validator  : <NULL>
#> @ properties :
#>  $ name: <character>          
#>  $ age : <integer> or <double>

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)
#> [1] "Dog"       "S7_object"

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").

Generics and methods

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)
#> [1] "Woof"

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)
#> [1] "Meow"

You get an error if you call the generic with a class that doesn’t have a method:

speak(1)
#> Error: Can't find method for `speak(<double>)`.

Method dispatch and inheritance

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
#> <Cat> class
#> @ parent     : <Pet>
#> @ constructor: function(name, age) {...}
#> @ validator  : <NULL>
#> @ properties :
#>  $ name: <character>          
#>  $ age : <integer> or <double>
Dog
#> <Dog> class
#> @ parent     : <Pet>
#> @ constructor: function(name, age) {...}
#> @ validator  : <NULL>
#> @ properties :
#>  $ name: <character>          
#>  $ age : <integer> or <double>

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)
#> [1] "Lola is 11 years old"
describe(fluffy)
#> [1] "Fluffy is 5 years old"

method(describe, Dog) <- function(x) {
  paste0(x@name, " is a ", x@age, " year old dog")
}
describe(lola)
#> [1] "Lola is a 11 year old dog"
describe(fluffy)
#> [1] "Fluffy is 5 years old"

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)
#> [1] "An S7 object"

Printing a generic will show you which methods are currently defined:

describe
#> <S7_generic> describe(x, ...) with 3 methods:
#> 1: method(describe, Dog)
#> 2: method(describe, S7_object)
#> 3: method(describe, Pet)

And you can use method() to retrieve the implementation of one of those methods:

method(describe, Pet)
#> <S7_method> method(describe, Pet)
#> function (x) 
#> {
#>     paste0(x@name, " is ", x@age, " years old")
#> }
#> <bytecode: 0x11631e0b0>

Learn more about method dispatch in vignette("generics-methods").

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.