Object Oriented Programming with aoos

2017-05-06

S4 Generics and Methods

%g% and %m% are two of the three binary operators, which link to the S4 system for generic functions. They provide alternatives to methods::setGeneric and methods::setMethod:

library("aoos")
# Standard definition for a generic without default:
numeric : strLength(x, ...) %g% standardGeneric("strLength")

# A method for x:character
strLength(x ~ character, ...) %m% { nchar(x) }

# Kind of the default method for x:ANY
strLength(x ~ ANY, ...) %m% { strLength(as.character(x)) }

# Check that it works:
strLength(123)
## [1] 3
strLength("ab")
## [1] 2

You may have noticed that we also constrained the return value of any method belonging to the generic strLength to be a numeric. There exist methods for objects of type character and ANY type.

In S4, methods can have defaults for arguments which are not formals of the generic. Otherwise the defaults of the generic are passed down to its methods. This is not changed: Define defaults for the generic. If a method has more arguments than its generic you can define defaults for them. For the shared argument names provide a class name. One exception to the rule is ... which can have no type.

S4 Types

The following presents the function %type% which is a link to S4s setClass. %type% tries to abstract a typical scenario of using setClass.

In the following We define two types. One is Test which has two fields, x and y. x is of type numeric, y is a list. Notice that you can either define a prototype (a default) for a field (for which the class is inferred), or you state the class explicitly using ~.

The second is Child and inherits the properties from Test. Thus it has also two fields, x and y, and in addition we say it inherits from type character. So Child is basically a character vector with two attributes:

Test(x ~ numeric, y = list()) %type% {
  stopifnot(length(.Object@x) == 1)
  stopifnot(.Object@x > 0)
  .Object
}

Test : character : Child() %type% .Object

Test(2)
## An object of class "Test"
## Slot "x":
## [1] 2
## 
## Slot "y":
## list()
Child("Hej", x = 2)
## An object of class "Child"
## [1] "Hej"
## Slot "x":
## [1] 2
## 
## Slot "y":
## list()

Notice that the right hand side of the expression is more or less the definition of the initialization method for a type. Arbitrary operations can be made during init, in the above example we formulate some assertions (x > 0 and scalar). The init method for type Child just returns the object itself named .Object (see the help page for methods::initialize to understand the naming).

S4 Type Unions

S4 provides also the possibility to construct type unions which are useful to allow a type to inherit from different types at the same time, e.g. a type which can either be a numeric or character. This feature is not yet complete, but here are some ways you can use it. For the definition of a type:

'numeric | character' : Either() %type% .Object
Either(1)
## An object of class "Either"
## [1] 1
Either("Hello World!")
## An object of class "Either"
## [1] "Hello World!"

In the definition of a field:

Either(x ~ numeric | character) %type% .Object
Either(1)
## An object of class "Either"
## Slot "x":
## [1] 1
Either("Hello World!")
## An object of class "Either"
## Slot "x":
## [1] "Hello World!"

In the definition of a generic or method:

'numeric | character' : complicatedFunction(x = 1) %g% as.character(x)
complicatedFunction(x ~ character | integer) %m% as.numeric(x)
complicatedFunction()
## [1] "1"
complicatedFunction("1")
## [1] 1
complicatedFunction(1L)
## [1] 1

Class Definitions with retList

Basically you define constructor functions. There is no formal class definition. The function body will define what members an object will have. You quit the function defining the return value using retList which is a generic constructor function. By default it will look at the environment from which it is called and convert that environment into a list. That list is returned and is an object. Names with a “.” are not part of the constructed list (by default).

library("aoos")
Employee <- function(.name, .salary) {
  "Common base class for all employees"
  
  print <- function(x, ...) {
    cat("Name  : ", .self$.name, "\nSalary: ", .self$.salary)
  }
  
  getName <- function() .name
  getSalary <- function() .self$.salary
  
  retList(c("Employee", "Print"))
  
}

peter <- Employee("Peter", 5)
peter
## Name  :  Peter 
## Salary:  5
peter$getName()
## [1] "Peter"
peter$getSalary()
## [1] 5

Here every instance is of class Employee and also inherits from class Print. This enables us to define the print method in the functions body and is equivalent to invoking the print method directly:

peter
## Name  :  Peter 
## Salary:  5
peter$print()
## Name  :  Peter 
## Salary:  5

Inheritance

You can inherit methods and fields from a super class, or rather an instance, because there is no formal class definition. Methods and fields can be replaced in the child, all member from the parent are also available for the methods of the child.

Manager <- function(.name, .salary, .bonus) {
  "Extending the Employee class"
  
  bonus <- function(x) {
    if (!missing(x)) .self$.bonus <- x
    .self$.bonus
  }
  
  print <- function(x, ...) {
    cat("Name  : ", .self$.name, "\nSalary: ", .self$.salary, 
        "\nBonus:", .self$.bonus)
  }
  
  retList("Manager", super = Employee(.name, .salary))
  
}

julia <- Manager("Julia", 5, 5 * 1e6)
julia
## Name  :  Julia 
## Salary:  5 
## Bonus: 5e+06
julia$getSalary()
## [1] 5
julia$bonus(10)
## [1] 10
julia
## Name  :  Julia 
## Salary:  5 
## Bonus: 10

Polymorphic Methods

In contrast to the defaults in S4, %g% and %m% have side effects in the environment they are called in. That means you can define generics which are local to a function or closure. Nice all by itself but it also extends the retList-idea of representing objects in R as demonstrated here:

Class <- function() {
  
  overloaded(x) %g% { 
    cat("This is the default ... \n")
    x 
  } 
  
  overloaded(x ~ numeric) %m% {
    cat("This is the method for 'numeric' values ... \n")
    x
  }
  
  retList("Class")
}

instance <- Class()
instance$overloaded(1)
## This is the method for 'numeric' values ...
## [1] 1
instance$overloaded("a")
## This is the default ...
## [1] "a"

The next question is how to inherit or extend an existing generic which is a member of a class?

Child <- function() {
  
  # Normally you would make the call to the parents constructor in the call
  # to retList. But here we need to access the elements directly during init...
  .super <- Class()
  
  # This points %m% to the generic (in .super) which should be extended:
  .super$overloaded(x ~ integer) %m% {
    cat("This is the method for 'integer' values ... \n")
    x
  }
  
  retList("Child", super = .super)
  
}

instance <- Child()
instance$overloaded(1)
## This is the method for 'numeric' values ...
## [1] 1
instance$overloaded("a")
## This is the default ...
## [1] "a"
instance$overloaded(1L)
## This is the method for 'integer' values ...
## [1] 1