The Big R-Book. Philippe J. S. De Brouwer
Чтение книги онлайн.
Читать онлайн книгу The Big R-Book - Philippe J. S. De Brouwer страница 46
![The Big R-Book - Philippe J. S. De Brouwer The Big R-Book - Philippe J. S. De Brouwer](/cover_pre848614.jpg)
It is also possible to create a class and set the attribute simultaneously with the function structure
.
structure()
my_object <- structure(list(), class = “boringClass”)
To create methods for S3 generic function, all we have to do is follow the syntax: <<generic function>>.<<class name>>
. R will then make sure that if an object of “class name” calls the generic function that then the generic function will dispatch the action to this specific function.
generic function
# print.account # Print an object of type ‘account’ # Arguments: # x -- an object of type account print.account <- function(x){ print(paste(“account holder”,x[[1]],sep=”: “)) print(paste(“balance “ ,x[[2]],sep=”: “)) } print(my_curr_acc) ## [1] “account holder: Philippe” ## [1] “balance : 100”
S3 objects are always build on other more elementary types. The function inherits (x, “classname”)
allows the user to determine if the class x inherits from the class “classname.”
You probably remember that R returned “internal” “generic”
as the class for some functions; so the class can be a vector. That means that the behaviour of that object can depend on different class-specific methods. The classes have to be listed from from most to least specific, so that the behaviour can follow this cascade and will always execute the most specific behaviour if it is present.
For example, the class of the glm()
object is c(“glm”, “lm”)
. This means that the most specific class is the generalised linear model, but that some behaviour they might inherit from linear models. When a generic function will be called, it will first try to find a glm-specific method. If that fails, it will look for the lm-method.
It is possible to provide a constructor function for an S3 class. This constructor function can, for example be used to check if we use the right data-type for its attributes.
constructor
# account # Constructor function for an object of type account # Arguments: # x -- character (the name of the account holder) # y -- numeric (the initial balance of the account # Returns: # Error message in console in case of failure. account <- function(x,y) { if (!is.numeric(y)) stop(“Balance must be numeric!”) if (!is.atomic(x)) stop(“Name must be atomic!!”) if (!is.character(x)) stop(“Name must be a string!”) structure(list(“name” = x, “balance” = y), class = “account”) } # create a new instance for Paul: paul_account <- account(“Paul”, 200) # print the object with print.account(): paul_account ## [1] “account holder: Paul” ## [1] “balance : 200”
The advantage of using the creator function for an instance of an object is obvious: it will perform some checks and will avoid problems later on. Unlike in message-passing OO implementations, the S3 implementation allows to bypass the creator function or worse it allows you to change the class all too easy. Consider the following example.
creator function
class(paul_account) <- “data.frame” print(paul_account) # R thinks now it is a data.frame ## [1] name balance ## <0 rows> (or 0-length row.names) paul_account[[2]] # the data is still correct ## [1] 200 class(paul_account) <- “account” print(paul_account) # back to normal: the class is just an attribute ## [1] “account holder: Paul” ## [1] “balance : 200”
6.2.2 Creating Generic Methods
We already saw how to create a method for a generic function by using simply the right naming convention. To add a new generic function, it is sufficient to call UseMethod()
.
UseMethod()
UseMethod() takes two arguments: the name of the generic function, and the argument to use for the method dispatch. If you omit the second argument it will dispatch on the first argument to the function. There is no need to pass any of the arguments of the generic to UseMethod()
, R will take care of that for you.
# add_balance # Dispatcher function to handle the action of adding a given amount # to the balance of an account object. # Arguments: # x -- account -- the account object # amount -- numeric -- the amount to add to the balance add_balance <- function(x, amount) UseMethod(“add_balance”)
This construct will do nothing else than trying to dispatch the real action to other functions. However, since we did not program them yet, there is nothing to dispatch to. To add those methods, it is sufficient to create a function that has the right naming convention.
# add_balance.account # Object specific function for an account for the dispatcher # function add_balance() # Arguments: # x -- account -- the account object # amount -- numeric -- the amount to add to the balance add_balance.account <- function(x, amount) { x[[2]] <- x[[2]] + amount; # Note that much more testing and logic can go here # It is not so easy to pass a pointer to a function so we # return the new balance: x[[2]]} my_curr_acc <- add_balance(my_curr_acc, 225) print(my_curr_acc) ## [1] 325
Leaving the code up to this level is not really safe. It is wise to foresee a default action in case the function add_balance()
is called with an object of another class.
# add_balance.default # The default action for the dispatcher function add_balance # Arguments: # x -- account -- the account object # amount -- numeric -- the amount to add to the balance add_balance.default <- function(x, amount) { stop(“Object provided not of type account.”) }
6.2.3 Method Dispatch
So in S3, it is not the object that has to know how a call to it has to be handled,