The Big R-Book. Philippe J. S. De Brouwer
:Formal class ‘Bnk’ [package “.GlobalEnv”] with 2 slots ## .. .. ..@ name : chr “HSBC Custody” ## .. .. ..@ phone: chr “123123123” ## ..@ holder : chr “Philippe” ## ..@ branch : chr “DUB01” ## ..@ opening_date: Date[1:1], format: “2019-02-21” isS4(my_inv_acc) ## [1] TRUE pryr::otype(my_inv_acc) ## [1] “S4”
The package methods
provides the function is()
. This function takes one object as argument, and lists all classes that the object provided as argument inherits from. Using is()
with two arguments will test if an object inherits from the class specified in the second argument.
is()
is(my_inv_acc) ## [1] “InvAcc” “Acc” is(my_inv_acc, “Acc”) ## [1] TRUE
The downside of the function centricOOsystem is that some things become a little subtle. Earlier we explained how to use isS4()
. There is no function isS3()
, but one will notice that is.S3()
exists. Now, you will understand that is.S3()
is the S3 specific method of the function is()
.
Looking up the source code can be helpful:
is.S3
## function(x){is.object(x) & !isS4(x)}
## <bytecode: 0x5634256e9f60>
There are many functions related to S4 objects, and it is not the aim to provide a full list however, the following might be useful for your code.
getGenerics() lists all S4 generics;
getGenerics()
getClasses() lists all S4 classes (it does however, include shim classes for S3 classes and base types);
getClasses()
showMethods() shows the methods for one or more generic functions, possibly restricted to those involving specified classes. Note that the argument where can be used to restrict the search to the current environment by using where = search();
showMethods()
6.3.7 Creating S4 Generics
R provides specific tools functions to create new generics and methods:
setGeneric() creates a new generic or converts an existing function into a generic.
setGeneric()
setMethod() creates a method for a generic function aligned to a certain class. It takes as argument the function, the signature of the class and the function definition.
setMethod()
We will build further on the example of the bank accounts as used in the previous sections of this chapter. As a first step, we can create methods to credit and debit a current account S4 object.
# setGeneric needs a function, so we need to create it first. # credit # Dispatcher function to credit the ledger of an object of # type ‘account’. # Arguments: # x -- account object # y -- numeric -- the amount to be credited credit <- function(x,y){useMethod()} # transform our function credit() to a generic one: setGeneric(“credit”) ## [1] “credit” # Add the credit function to the object CurrAcc setMethod(“credit”, c(“CurrAcc”), function (x, y) { new_bal <- x@balance + y new_bal } ) ## [1] “credit” # Test the function: my_curr_acc@balance ## [1] 500 my_curr_acc@balance <- credit(my_curr_acc, 100) my_curr_acc@balance ## [1] 600
While the functionality for credit
might seem trivial, in reality crediting an account will require a lot of checks (e.g. sanctioned countries and terrorist financing). So, let us create now a little more useful example with a function debet()
, because before debiting an account, one will need to check if there is enough balance.
# debet # Generic function to debet an account # Arguments: # x -- account object # y -- numeric -- the amount to be taken from the account # Returns # confirmation of action or lack thereof debet <- function(x,y){useMethod()} # Make it a generic function that verifies the balance # before the account a debet is booked. setGeneric(“debet”) ## [1] “debet” # Add the debet() function as a method for objects of type CurrAcc setMethod(“debet”, c(“CurrAcc”), function (x, y) { if(x@balance >= y) { new_bal <- x@balance + y} else { stop(“Not enough balance.”) } new_bal } ) ## [1] “debet” # Test the construction: my_curr_acc@balance # for reference ## [1] 600 my_curr_acc@balance <-debet(my_curr_acc, 50) my_curr_acc@balance # the balance is changed ## [1] 650 # We do not have enough balance to debet 5000, so the # following should fail: my_curr_acc@balance <-debet(my_curr_acc, 5000) ## Error in debet(my_curr_acc, 5000): Not enough balance. my_curr_acc@balance # the balance is indeed unchanged: ## [1] 650
If you want to overload an existing function such as union()
and exp()
, then you should of course not define the function first – as in the first line in the aforementioned code. Doing so will make its original definition unavailable.
6.3.8 Method Dispatch
In its most simple form, S4 dispatching is similar to S3 dispatching. However, the implementation for S4 is more powerful. When an S4 generic function dispatches on a single class with a single parent, then S4 method dispatch is no different from the S3 method dispatch.
For practical use, the main difference is how default values are defined. In S4, there is a special class “ANY” that functions as the default.
Similarly to S3, S4 knows about group generics. Further, details can be found in the function documentation (visualize the documentation – as usual – with the command ?S4groupGeneric
. There is also a function to call the “parent method”: callNextMethod()
.
S4groupGeneric()
callNextMethod()
The main advantage of S4 is that it is possible to dispatch on multiple arguments (though it all becomes a little more complicated and abstract). That is the case when you have multiple parent classes. The rules can be found in the documentation of the function Methods
.
Methods()
These aspects require utmost care. Personally, the author believes that this is where the S4 OO implementation stops