The Big R-Book. Philippe J. S. De Brouwer
alt="image"/> Warning – Silent setting to default
Did you notice that R is silent about the missing balance? This is something to be careful with. If you forget that a default value has been assigned then this might lead to confusing mistakes.
An empty value for balance is not very useful and it can even lead to errors. Therefore, it is possible to assign default values with the function prototype
when creating the class definition.
prototype()
setClass(“CurrAcc”, representation(interest_rate = “numeric”, balance = “numeric”), contains = “Acc”, prototype(holder = NA_character_, interst_rate = NA_real_, balance = 0)) x_account <- new(“CurrAcc”, # no holder # no interest rate # no balance branch = “LDN12”, opening_date= as.Date(“2018-12-01”)) x_account # show what R did: ## An object of class “CurrAcc” ## Slot “interest_rate”: ## numeric(0) ## ## Slot “balance”: ## [1] 0 ## ## Slot “holder”: ## [1] NA ## ## Slot “branch”: ## [1] “LDN12” ## ## Slot “opening_date”: ## [1] “2018-12-01”
Most programming languages implement an OO system where class definitions are created when the code is compiled and instances of classes are created at runtime. During runtime, it is not possible to change the class definitions.
However, R is an interpreted language that is interactive and functional. The consequence is that it is possible to change class definitions at runtime (“while working in the R-terminal”). So it is possible to call setClass()
again with the same object name, and R will assume that you want to change the previously defined class definition and silently override it. This can lead, for example, to the situation where different objects pretend to be of the same class, while they are not.
To make sure that a previous class definition cannot be changed add sealed = TRUE
to the call to setClass()
It is common practice to use “UpperCamelCase” for S4 class names.a Using a convention is always a good idea, using one that many other people use is even better. This convention avoids any confusion with the dispatcher functions that use the dot as separator.
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 user dt <- 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()
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++
6.3.5 The .Data slot
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 = “[email protected]”, 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)