# 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 `name` of the class, supplied in the first argument.
- The class `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
    #> <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 retrieve 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: 0x1335c0130>

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