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

Скачать книгу

<- list(“name” = “Philippe”, “balance” <- 100) class(my_curr_acc) <- “account” # set the class attribute otype(my_curr_acc) # it is an S3 object ## [1] “S3” class(my_curr_acc) # the class type is defined above ## [1] “account” # Note that the class attribute is not visible in the structure: str(my_curr_acc) ## List of 2 ## $ name: chr “Philippe” ## $ : num 100 ## - attr(*, “class”)= chr “account”

      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”

      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

       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

Скачать книгу