R Markdown and code available on GitHub

Object oriented programming in R is possible but is hardly advertized and there is also the fact that there is a proliferation of ways to do OO in R. The whole domain is rather confusing. If you wish to understand the S3, S4, RC type of OO then this is an excellent article highlighting the various approaches.

A relatively recent approach and one that in my opinion is the most clean sits in the R6 library. It resembles closely the way one would do some sort of OO in JavaScript with the list as the replacement for JSON and literal objects. Maybe just like JavaScript, however, the R language was never meant to be turned into a full-fledged OO paradigm. The thing is that when you write thousands of lines of R and work with many on the same codebase you need more structure.

In any case, here is a short intro to R6 classes. Hopefully it helps turning your R bits into a more approachable style.

In order to access R6 features you need to load the library

library(R6)

At the most basic level you can define an empty Person class like so

Person = R6Class("Person")

and instantiate a Person via the new() method call

p = Person$new()

Meaning that there is always a default constructor available. If you want a custom one you can add it through the publc initialize method

Person = R6Class("Person",
                 public = list(
                   initialize = function(name){ print(paste("A new Person with name '", name, "'")) }
                   )
                 )

Obviously you want to hold this variable in a field

Person = R6Class("Person",
                
                 public = list(
                   Name = NA,
                   initialize = function(name){self$Name = name }
                   )
                 )
p = Person$new("John")
# what is the name?
paste("This person is called", p$Name)
# change it
p$Name = "Maria"
paste("This person is now called", p$Name)

If you want to have getter-setter method you need to use the so-called active bindings. This is a get-set just like in JavaScript which returns the field if no value is provided but sets the field if one is given. If the method has no argument it’s presumed a read-only property.

rson = R6Class("Person",
                private = list(
                  name = NA
                  ),
                active = list(
                  Name = function(name){
                    if(missing(name)) return(private$name)
                    private$name = name
                  }
                  ),
                public = list(                  
                   initialize = function(name){private$name = name }
                   )
                 )
p = Person$new("John")
# what is the name?
p$Name
# change it
p$Name = "Maria"

Note the usage of private in order to access the private member.
The next bit of OO on the list is having methods, which can be either public or private

Person = R6Class("Person",
                private = list(
                  name = NA,
                  speakInternal = function(){print("A private message.")}
                  ),
                active = list(
                  Name = function(name){
                    if(missing(name)) return(private$name)
                    private$name = name
                  }
                  ),
                public = list(                  
                   initialize = function(name){private$name = name },
                   Speak = function(){print("A public message.")}
                   )
                 )
p = Person$new("John")

The big thing of OO is inheritance and it goes like this

Hero = R6Class("Hero",
               inherit = Person
               )

If you try to instantiate with new() you will receive an error since the default constructor needs a name. So, if you want to define a ctor in this inherited class you can call the base ctor via the super keyword

Hero = R6Class("Hero",
               inherit = Person,
               public = list(
                 initialize = function(){
                   super$initialize("Batman")
                 }
                 )
               )

All of this should be familair if you worked with Java, JavaScript and other languages.
Similarly with overriding methods;

Hero = R6Class("Hero",
               inherit = Person,
               public = list(
                 initialize = function(){
                   super$initialize("Batman")
                 },
                 Speak = function(){
                                    print("I am a hero even if the base-Person says,")
                                    super$Speak()
                                    }
                 )
               )

Just like JavaScript the framework allows you to add methods on the fly via the $set keyword

Hero$set("public", "RealName", function(){"Bruce Wayne"})

Finally, you can of course use more complex objects in the field members but you need to remember that if you instantiate it inline they will behave like static members. In this example the Chief field is static across instances

Team = R6Class("Team", public = list(
    Chief = Person$new("Mike")
  ))
team1 = Team$new()
print(paste("The chief in team 1 is ", team1$Chief$Name))
team2 = Team$new()
print(paste("The chief in team 2 is also", team2$Chief$Name))

If you want a regular instance you need to instantiate it in the constructor;

Team = R6Class("Team", public = list(
    Chief = NA,
    initialize = function(name){self$Chief =  Person$new(name)  }
  ))
team1 = Team$new("Mike")
print(paste("The chief in team 1 is", team1$Chief$Name))
team2 = Team$new("Bill")
print(paste("The chief in team 2 is", team2$Chief$Name))