UpperCamelCase
lowerCamelCase
snake_case
dot.separator
6.3.4 Constructor functions
Constructor functions should be given the same name as the class and it allows much more testing and action than the standard new()
function.
This is the constructor function to create a new instance of CurrAcc:.CurrAcc <- function(holder, interest_rate # branch we know from the user # balance should be 0 # opening_date is today) { error_msg =“Invalid input while creating an account\n” if( is.atomic(holder) & !is.character(holder)) { stop(error_msg, “Invalid holder name.”) } if( !( is.atomic(interest_rate) & is.numeric(interest_rate) &(interest_rate >=) &(interest_rate <0.1))) { stop(error_msg, “Interest rate invalid.”) } br <-“PAR01” # pretending to find balance by looking up userdt <- as.Date( Sys.Date()) new(“CurrAcc”, holder = holder, interest_rate = interest_rate, balance=, branch = br, opening_date= dt) } # Create a new account:lisa_curr_acc <- .CurrAcc(“Lisa”, 0.01) lisa_curr_acc ## An object of class “CurrAcc” ## Slot “interest_rate”: ## [1] 0.01 ## ## Slot “balance”: ## [1] 0 ## ## Slot “holder”: ## [1] “Lisa” ## ## Slot “branch”: ## [1] “PAR01” ## ## Slot “opening_date”: ## [1] “2020-01-30”
Sys.Date()
Hint – Calling the constructor function
Unlike C++, for example a call to new()
will not automatically invoke the constructor function (its existence is not enough to invoke it automatically). Make it a good habit to always use explicitly the constructor function for an S4 objects (provided it exists of course).
C++
If an S4 object inherits from an S3 class or a base type, R will give it a special .Data
slot that contains the data of this underlying object (S3 or base type):
# Here is the prototype of a dataset that holds some extra # information in a structured way. setClass(“myDataFrame”, contains = “data.frame”, slots = list(MySQL_DB = “character”, MySQL_tbl = “character”, data_owner = “character” ) ) xdf <- new(“myDataFrame”, data.frame( matrix(1 :9, nrow=3)), MySQL_DB = “myCorporateDB@102.12.12.001”, MySQL_tbl = “tbl_current_accounts”, data_owner = “customer relationship team”) xdf @.Data ## [[1]] ## [1] 1 2 3 ## ## [[2]] ## [1] 4 5 6 ## ## [[3]] ## [1] 7 8 9 xdf @data_owner ## [1] “customer relationship team”
runif()
setClass()
new()
6.3.6 Recognising Objects, Generic Functions, and Methods
While we casually used already isS4()
to check if an object is S4, there are multiple ways to find out if an object is S4:
str() will report it as an S4 class,
str()
isS4() returns TRUE, note that this is not the same as is.S3(), this is the class-specific method of the function is(),
isS4()
pryr::otype() returns S4.
otype()
S4 generics and methods are also easy to identify because they are S4 objects with well-defined classes.
There aren't any S4 classes in the commonly used base packages (stats, graphics, utils, datasets, and base), so we will continue to use our previous example of the bank accounts.
str(my_inv_acc) ## Formal class ‘InvAcc’ [package “.GlobalEnv”] with 4 slots ## ..@ custodian :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
Note – Nuances in the OO system
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)}
##
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 creditedcredit <- 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
Читать дальше