# 16 R6

## 16.1 Introduction

This chapter describes the R6 object system. Unlike S3 and S4, it provides encapsulated OO, which means that:

• R6 methods belong to objects, not generics.

• R6 objects are mutable: the usual copy-on-modify semantics do not apply.

These properties make R6 objects behave more like objects in programming languages such as Python, Ruby and Java. This does not mean that R6 is good, and S3 and S4 are bad, it just means that R has a different heritage than most modern mainstream programming languages.

R6 is very similar to a built-in OO system called reference classes, or RC for short. I’m going to teach you R6 instead of RC for four reasons:

• R6 is much simpler. Both R6 and RC are built on top of environments, but while R6 uses S3, RC uses S4. R6 is only ~500 lines of R code (and ~1700 lines of tests!). We’re not going to discuss the implementation in depth here, but if you’ve mastered the contents of this book, you should be able to read the source code and figure out how it works.

• RC mingles variables and fields in the same stack of environments so that you get (field) and set fields (field <<- value) like regular values. R6 puts fields in a separate environment so you get (self$field) and set (self$field <- value) with a prefix. The R6 approach is more verbose but is worth the tradeoff because it makes code easier to understand. It also makes inheritance across packages simpler and more robust.

• R6 is much faster than RC. Generally, the speed of method dispatch is not important outside of microbenchmarks but R6 is substantially better than RC. Switching from RC to R6 yielded substantial performance in shiny. vignette("Performance", "R6") provides more details on the performance.

• Because the ideas that underlie R6 and RC are similar, it will only require a small amount of additional effort to learn RC if you need to.

### Prequisites

Because R6 is not built into base R, you’ll need to install and load a package in order to use it:

If you’d like to learn more about R6 after reading this chapter, the best place to start is the vignettes included in the package. You can list them by calling browseVignettes(package = "R6").

## 16.2 Classes and methods

R6 only needs a single function call to create both the class and its methods: R6::R6Class(). And this is the only function from the package that you’ll ever use! The following example shows the two most important arguments:

• The first argument is the classname. It’s not strictly needed, but it improves error messages and makes it possible to also use R6 objects with S3 generics. By convention, R6 classes use UpperCamelCase.

• The second argument, public, supplies a list of methods (functions) and fields (anything else) that make up the public interface of the object. By convention, methods and fields use snake_case. Methods can access the methods and fields of the current object via self$. You should always assign the result of R6Class() into a variable with the same name as the class. This creates an R6 object that defines the R6 class: You construct a new object from the class by calling the new() method. Methods belong to R6 objects so you use$ to access new():

You can then call methods and access fields with $: In this class, the fields and methods are public which means that you can get or set the value of any field. Later, we’ll see how to use private fields and methods to prevent casual access to the internals of your class. To make it clear when we’re talking about fields and methods as opposed to variables and functions, when referring to them in text, we’ll prefix with$. For example, the Accumulate class has field $sum and method$add().

### 16.2.1 Method chaining

$add() is called primarily for its side-effect of updating$sum.

Side-effect R6 methods should always return self invisibly. This returns the “current” object and makes it possible to chain together multiple method calls:

Alternatively, for long chains, you can spread the call over multiple lines:

This technique is called method chaining and is commonly used in encapsulated OO languages (like Python and JavaScript) to create fluent interfaces. Method chaining is deeply related to the pipe, and we’ll discuss the pros and cons of each approach in pipe vs message-chaining tradeoffs.

### 16.2.2 Important methods

There are two important methods that will be defined for most classes: $initialize() and$print(). You don’t have to provide them, but it’s a good idea to do so because they will make your class easier to use.

$initialize() overrides the default behaviour of$new(). For example, the following code defines an R6 Person class, similar to the S4 equivalent in S4. Unlike S4, R6 provides no checks for object type by default. $initialize() is a good place to check that name and age are the correct types. If you have more expensive validation requirements, implement them in a separate$validate() and only call when needed.

Defining $print() allows you to override the default printing behaviour. As with any R6 method called for its side effects,$print() should return invisible(self).

This code illustrates an important aspect of R6. Because methods are bound to individual objects, the previously created hadley does not get this new method:

Indeed, from the perspective of R6, there is no relationship between hadley and hadley2. This can make interactive experimentation with R6 confusing. If you’re changing the code and can’t figure out why the results of method calls aren’t changed, make sure you’ve re-constructed R6 objects with the new class.

There’s a useful alternative to $print(): implement$format(), which should return a character vector. This will automatically be used by both print() and format() S3 generics.

### 16.2.3 Adding methods after creation

Instead of continuously creating new classes, it’s also possible to modify the methods of an existing class. This is useful when exploring interactively, and when you have a class with many functions that you’d like to break up into pieces.

Once the class has been defined, you can add elements to it with $set(), supplying the visibility (more on that below), the name, and the component.$set() will not overwrite an existing method unless you explicitly ask for it:

Also note that adding methods will only affect new objects generated from the class. It does not retrospectively apply to existing objects:

### 16.2.4 Inheritance

To inherit behaviour from an existing class, provide the class object to the inherit argument:

Note that $add() overrides the implementation in the superclass, but we can access the previous implementation through super$. Any methods which are overridden will automatically call the implementation in the parent class.

Like S3, R6 only supports single inheritance: you cannot supply a vector of classes to inherit.

### 16.2.5 Introspection

Every R6 object has an S3 class that reflects the hierarchy of R6 classes. This means that the easiest way to determine the class (and all classes it inherits from) is to use class():

The S3 hierarchy includes the base “R6” class. This provides common behaviour, including an print.R6() method which calls $print() or$format(), as described above.

You can list all methods and fields with names():

### 16.4.2 Finalizer

One useful property of reference semantics is that it makes sense to think about when an R6 object is finalised, i.e. when it’s deleted. This doesn’t make sense for S3 and S4 objects because copy-on-modify semantics mean that there may be many transient versions of an object. For example, in the following code, there are actually two factor objects: the second is created when the levels are modified, leaving the first to be destroyed at the next garbage collection.

Since R6 objects are not copied-on-modify they will only get deleted once, and it makes sense to think about $finalize() as a complement to$initialize(). Finalizers usually play a similar role to on.exit(), cleaning up any resources created by the initializer. For example, the following class wraps up a temporary file, automatically deleting it when the class is finalised.

The finalise method will be run when R exits, or by the first garbage collection after the object has been removed. Generally, this will happen when it happens, but it can occasionally be useful to force a run with an explicit call to gc().

### 16.4.3 R6 fields

A final consequence of reference semantics can crop up where you don’t expect it. Beware of setting a default value to an R6 class: it will be shared across all instances of the object. This is because the child object is only initialized once, when you defined the class, not each time you call new.

You can fix this by creating the object in \$initialize():