%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) 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.
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) 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 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) Either("Hello World!")
In the definition of a field:
Either(x ~ numeric | character) %type% .Object Either(1) Either("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() complicatedFunction("1") complicatedFunction(1L)
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 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:
peter peter$print()
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 julia$getSalary() julia$bonus(10) julia
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) instance$overloaded("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) instance$overloaded("a") instance$overloaded(1L)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.