Skip to content
initdch edited this page Apr 11, 2012 · 12 revisions

The R5 object system

R has three object oriented (OO) systems: S3, S4 and R5. This page describes the new reference based class system, colloquially known as R5.

R5 is new in R 2.12. They fill a long standing need for mutable objects that had previously been filled by non-core packages like R.oo, proto and mutatr. While the core functionality of R5 is solid, reference classes are still under active development and some details will change. The most up-to-date documentation for R5 can always be found in ?ReferenceClasses.

There are two main differences between R5 and S3 and S4:

  • R5 objects use message-passing OO
  • R5 objects are mutable: the usual R copy on modify semantics do not apply

These properties makes this object system behave much more like Java and C#. Surprisingly, the implementation of R5 is almost entirely in R code - they are a combination of S4 methods and environments. This is a testament to the flexibility of S4.

Particularly suited for: simulations where you're modelling complex state, GUIs.

Note that when using reference based classes we want to minimise side effects, and use them only where mutable state is absolutely required. The majority of functions should still "functional", and side effect free. This makes code easier to reason about (because you don't need to worry about methods changing things in surprising ways), and easier for other R programmers to understand.

Limitations: can't use enclosing environment - because that's used for the object.

Classes and instances

Creating a new reference based class is straightforward: you use setRefClass. Unlike setClass from S4, you want to keep the results of that function around, because that's what you use to create new objects of that type:

# Or keep reference to class around.
Person <- setRefClass("Person")
Person$new()

A reference class has three main components, given by three arguments to setRefClass:

  • contains, the classes which the class inherits from. These should be other reference class objects:

      setRefClass("Polygon")
      setRefClass("Regular")
    
      # Specify parent classes
      setRefClass("Triangle", contains = "Polygon")
      setRefClass("EquilateralTriangle", 
        contains = c("Triangle", "Regular"))
    
  • fields are the equivalent of slots in S4. They can be specified as a vector of field names, or a named list of field types:

      setRefClass("Polygon", fields = c("sides"))
      setRefClass("Polygon", fields = list(sides = "numeric"))
    

    The most important property of R5 objects is that they are mutable, or equivalently they have reference semantics:

      Polygon <- setRefClass("Polygon", fields = c("sides"))
      square <- Polygon$new(sides = 4)
      
      triangle <- square
      triangle$sides <- 3
      
      square$sides        
    
  • methods are functions that operate within the context of the object and can modify its fields. These can also be added after object creation, as described below.

      setRefClass("Dist")
      setRefClass("DistUniform", c("a", "b"), "Dist", methods = list(
        mean <- function() {
          (a + b) / 2
        }
      ))
    

You can also add methods after creation:

# Instead of creating a class all at once:
Person <- setRefClass("Person", methods = list(
  say_hello = function() message("Hi!")
))

# You can build it up piece-by-piece
Person <- setRefClass("Person")
Person$methods(say_hello = function() message("Hi!"))

It's not currently possible to modify fields because adding fields would invalidate existing objects that didn't have those fields.

The object returned by setRefClass (or retrieved later by getRefClass) is called a generator object. It has methods:

  • new for creating new objects of that class. The new method takes named arguments specifying initial values for the fields

  • methods for modifying existing or adding new methods

  • help for getting help about methods

  • fields to get a list of fields defined for class

  • lock locks the named fields so that their value can only be set once

  • accessors a convenience method that automatically sets up accessors of the form getXXX and setXXX.

Methods

R5 methods are associated with objects, not with functions, and are called using the special syntax obj$method(arg1, arg2, ...). (You might recall we've seen this construction before when we called functions stored in a named list). Methods are also special because they can modify fields. This is different

We've also seen this construct before, when we used closures to create mutable state. Reference classes work in a similar manner but give us some extra functionality:

  • inheritance
  • a way of documenting methods
  • a way of specifying fields and their types

Modify fields with <<-. Will call accessor functions if defined.

Special fields: .self (Don't use fields with names starting with . as these may be used for special purposes in future versions.)

initialize

Common methods

Because all R5 classes inherit from the same superclass, envRefClass, they a have common set of methods:

  • obj$callSuper:

  • obj$copy: creates a copy of the current object. This is necessary because R5 classes don't behave like most R objects, which are copied on assignment or modification.

  • obj$field: named access to fields. Equivalent to slots for S4. obj$field("xxx") the same as obj$xxx. obj$field("xxx", 5) the same as obj$xxx <- 5

  • obj$import(x) coerces into this object, and obj$export(Class) coerces a copy of obj into that class. These should be super classes.

  • obj$initFields

Documentation

Python style doc-strings. obj$help().

In packages

Note: collation Note: namespaces and exporting

In the wild

Rook package. Scales package?

Clone this wiki locally