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 raisex
to the power ofexp
. If you change the value ofexp
after defining the first function, it will change what powerx
is raised to later on, even though the function was defined beforeexp
was changed.