--- title: "Object Oriented Programming with aoos" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{S4SyntacticSugar} %\VignetteEngine{knitr::rmarkdown} %\usepackage[utf8]{inputenc} --- ## 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`: ```{r} 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) strLength("ab") ``` 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: ```{r} Test(x ~ numeric, y = list()) %type% { stopifnot(length(.Object@x) == 1) stopifnot(.Object@x > 0) .Object } Test : character : Child() %type% .Object Test(2) Child("Hej", x = 2) ``` 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: ```{r} 'numeric | character' : Either() %type% .Object Either(1) Either("Hello World!") ``` In the definition of a field: ```{r} Either(x ~ numeric | character) %type% .Object Either(1) Either("Hello World!") ``` In the definition of a generic or method: ```{r} 'numeric | character' : complicatedFunction(x = 1) %g% as.character(x) complicatedFunction(x ~ character | integer) %m% as.numeric(x) complicatedFunction() complicatedFunction("1") complicatedFunction(1L) ``` ## 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). ```{r} 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 peter$getName() peter$getSalary() ``` 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: ```{r} peter peter$print() ``` ### 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. ```{r} 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 julia$getSalary() julia$bonus(10) julia ``` ### 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: ```{r} 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) instance$overloaded("a") ``` The next question is how to inherit or extend an existing generic which is a member of a class? ```{r} 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) instance$overloaded("a") instance$overloaded(1L) ```