aoosClasses

aoos classes

Most of what this package can do is inspired by the R-package R6. Unlike R6, aoos relies on the methods package and the S4 class system. It was not written to be superior with respect to efficiency. Use aoos if you want to use object oriented programming as a means to organise your source code.

  • An aoos class definition is a R expression. You can write code and decide which objects are public.
  • You can use auto-complete to see public member functions.
  • You can use features of S4 (method dispatch).

All examples in the following are adapted from the R6-package.

Basics

There is one function you have to remember:

  • defineClass: To define a class.

Everything you define inside the class definition will be public, objects with a leading dot in the name will be private. This can be overwritten by the functions private and public. Every public member will inherit from class function. A “public field” is the get/set method for an object hidden behind a closure (see the class Accessor if you dislike this). An exception are objects which inherit from class ‘environment’ so basically all reference classes in R. Those you can access without get/set methods.

Here is a simple class definition:

library(aoos)

Person <- defineClass("Person", {
  
  personName <- ""
  
  init <- function(name) {
    self$personName(name)
    self$greet()
    }
  
  greet <- function() {
    cat(paste0("Hello, my name is ", self$personName(), ".\n"))
    }
  
  })

The return value of defineClass is the constructor function which is assigned to the name Person. All arguments to the constructor will be passed on to init if you defined it.

ann <- Person("Ann")
## Hello, my name is Ann.
ann
## Class: Person
## public member:
##   greet 
##   init 
##   personName 
##   self
ann$personName()
## [1] "Ann"
ann$personName("not Ann")
ann$greet()
## Hello, my name is not Ann.

Inside the class definition you can use self but you do not have to; It just might be more explicit. You can also use the super-assignment operator <<- to replace private fields. The following illustrates the difference:

Person <- defineClass("Person", {
  
  .personName <- "" # .personName is private
  
  init <- function(name) {
    self$.personName <- name # option 1
    .personName <<- name # option 2
    greet()
    }
  
  greet <- public(function() {
    cat(paste0("Hello, my name is ", .personName, ".\n")) # before: personName()
    })
  })

What you have to keep in mind is, that a public field is always hidden behind a get/set method and needs to be treated as a function. Private fields are just R objects and follow copy-on-modify semantics.

Predefined methods

aoos classes inherit from class ‘aoos’. For that class some S4-methods are defined you can use:

  • show which prints the public members of an object to the console
  • summary which tries to give a clue how much memory is used by an object

For every class you define with defineClass the S4 method initialize is set. Do not change it. You can also use the S4 constructor new to create a new instance of your class.

new("Person", "Ann")
## Hello, my name is Ann.
## Class: Person
## public member:
##   greet 
##   init 
##   personName 
##   self

Inheritance

A class defined by defineClass can inherit from other aoos classes. The expression (expr in defineClass) is first evaluated for the parent and then for the child in the same environment. So you can override methods and fields.

Queue <- defineClass("Queue", {

  .queue <- list()
  
  init <- function(...) {
      for (item in list(...)) self$add(item)
    }
  
  add <- function(x) {
      .queue <<- c(.queue, list(x))
      invisible(self)
    }

  remove <- function() {
      if (queueIsEmpty()) return(NULL)
      head <- .queue[[1]]
      .queue <<- .queue[-1]
      head
    }

  queueIsEmpty <- function() length(.queue) == 0
  
})

HistoryQueue <- defineClass("HistoryQueue", contains = "Queue", {
  
  .head_idx <- 0
  
  remove <- function() {
    if ((length(.queue) - .head_idx) == 0) return(NULL)
    self$.head_idx <- .head_idx + 1
    .queue[[.head_idx]]
    }
  
  show <- function() {
      cat("Next item is at index", .head_idx + 1, "\n")
      for (i in seq_along(.queue)) {
        cat(i, ": ", .queue[[i]], "\n", sep = "")
      }
    }
  
  })

q <- Queue(5, 6, "foo")
q$remove()
## [1] 5
q
## Class: Queue
## public member:
##   add 
##   init 
##   queueIsEmpty 
##   remove 
##   self
hq <- HistoryQueue(5, 6, "foo")
hq
## Class: HistoryQueue
## public member:
##   add 
##   init 
##   queueIsEmpty 
##   remove 
##   self 
##   show
hq$show()
## Next item is at index 1 
## 1: 5
## 2: 6
## 3: foo
hq$remove()
## [1] 5
hq$show()
## Next item is at index 2 
## 1: 5
## 2: 6
## 3: foo
hq$remove()
## [1] 6

Since these classes use the S4 system you can use setMethod to define additional methods like show. Define S4-methods always outside the defineClass function as expr will be evaluated every time a new instance is created.

setMethod("show", "HistoryQueue", function(object) {
  object$show()
})

hq
## Next item is at index 3 
## 1: 5
## 2: 6
## 3: foo

Inheritance across packages

This is a little tricky. expr is evaluated in a new environment whenever you create a new instance. If one class inherits from another the same environment is used for the evaluation. Thus, objects of the parent can be replaced and the scoping of the parent is preserved; functions defined for the parent and the child share the same environment. The parent environment of an aoos class is the environment in which defineClass (for the parent) is called. This will typically be a package namespace. If you define class B in package pkgB which inherits from a class A from package pkgA class B will have the same scoping as a class in pkgA. Whenever you want to use functions from pkgB in class B you have to define them as part of the class definition or refer to them with pkgB::"functionName" or pkgB:::"functionName".