Object Oriented and Functional Programming

Introduction

Object oriented programming (OOP) is a programming language model that defines objects; which are elements in R that contain attributes, or fields, which have some specification in the definition of the object itself. Objects are defined in advance, and are very useful in conceptualising coding goals, and allowing the end-user a better experience when using your functions and/or code.

Base R has three different ways of defining objects, which are the three different models:

  • S3
  • S4
  • Reference Class

All of which have their merits and disadvantages. S3 is the simplest model, and is useful for defining a basic object. S4 is more complex, as classes have to be defined explicitly, but adds more clarity and allows inclusion of integrity checks. Reference Class is more complex again, but further improves on teh structure of the class definition, through incorporation of a higher degree of encapsulation.

Examples: Dealing Cards in Reference Class

For this example, I will be using the Reference Class model. This example is concerned with being able to deal a card from a standard card deck. To start with, we make a class called Card which will contain two properties; the suit and the value. This is set up as follows.

Card <- setRefClass("Card",
                    fields = c(
                      suit = "character",
                      value = "numeric",
                      pairs = "numeric"
                    ))

The setRefClass function is used to create this class, and it has the two attributes that are required of a standard card. We can set up a function to deal a random card from a deck by now specifying two more commands.

dealHand = function(n){
  y <- Card$new(n)
  return(y)
}

Card$methods(
  initialize = function(n){
    suits <- c("Diamonds","Hearts","Clubs","Spades")
    s <- sample(0:51, n)
    
    .self$suit <- suits[(s %/% 13) + 1]
    .self$value <-  (s %% 13)+1
  }
)

The function dealHand has its only input as n, which is the size of the hand. The assignment here is given by the initialize method in the $methods substructure of Card. By setting the method of initialize to randomly sample both value and suit, this will deal a random card every time that dealCard(n) is run. For example:

dealHand(5)
## Reference class object of class "Card"
## Field "suit":
## [1] "Diamonds" "Spades"   "Clubs"    "Hearts"   "Spades"  
## Field "value":
## [1]  9  7  5  4 11
## Field "pairs":
## numeric(0)

We can also add another method that will recognise if there are any pairs in the hand that has been dealt. This is done by adding an additional method to Card$methods:

Card$methods(
  initialize = function(n){
    suits <- c("Diamonds","Hearts","Clubs","Spades")
    s <- sample(0:51, n)
    
    .self$suit <- suits[(s %/% 13) + 1]
    .self$value <-  (s %% 13) + 1
  },
  getPairs = function(){
    .self$pairs <- as.numeric(names(table(.self$value))[table(.self$value)>=2])
  }
)

So that now, if we are dealt a hand , we can see how many pairs there are in the hand, and what the value of the pair is:

set.seed(2)
hand = dealHand(5)
hand$getPairs()
hand
## Reference class object of class "Card"
## Field "suit":
## [1] "Hearts"   "Hearts"   "Diamonds" "Spades"   "Clubs"   
## Field "value":
## [1]  8  2  6 11  6
## Field "pairs":
## [1] 6

Our hand here contains the 8 of Hearts, the 2 of Hearts, the 6 of Diamonds, the Jack (11) of Spades, and the 6 of Clubs. So we have two sixes, and one pair. The class hand now has a new entry, a field named pairs, which contains the number 6, showing 6 is our only pair.

Functional Programming

Functional programmings is (obviously) focused on using functions. We call functions ‘first class’, because they

  • Can be embedded into lists/dataframes
  • Can be an argument to another function
  • Can be returned by other functions
  • And more

You can consider functions as another type of variable, as you would store and use them in similar ways. For example, consider the list of functions

mylist = list(add_function = function(x,y) x+y, subtract_function = function(x,y) x-y)
mylist$add_function(1,2)
## [1] 3
mylist$subtract_function(2,1)
## [1] 1

You can see how this could be useful, in setting a list of different functions. Applications could include including a list of link functions in some form of regression, or basis functions. Functions can also return other functions, consider

make_link_function = function(which_f){
  if(which_f == "exponential") f = function(x) exp(x)
  if(which_f == "identity") f = function(x) x
  return(f)
}
link_function = make_link_function("exponential")
link_function(1:5)
## [1]   2.718282   7.389056  20.085537  54.598150 148.413159

This is a style of function output that could be used in making a general linear model, for example, to link the parameter to the predictor.

In regards to functional programming, there are some important definitions:

  • Pure functions: A function which always gives the same output for the same inputs, and does not have any side effects. An impure function will be one that, for example, generates (pseudo) random numbers.
  • Closures: A function that outputs another function, but contains a closed variable that is defined only within the main function itself and not in global variables.
  • Lazy evaluation: The inputs to a function come from the global variables, instead of what was previously defined. For example the function function(exp) function(x) x^exp returns a function that will raise x to the power of exp. If you change the value of exp after defining the first function, it will change what power x is raised to later on, even though the function was defined before exp was changed.
Previous
Next