Object oriented programming

Dr. Alexander Fisher

Duke University

January 25, 2023

Review of types

R’s type hierarchy

  • from most to least complex: class \(\rightarrow\) mode \(\rightarrow\) type
value typeof() mode() class()
TRUE logical logical logical
1 double numeric numeric
1L integer numeric integer
"A" character character character
NULL NULL NULL NULL
list(1, "A") list list list
factor("A") integer numeric factor
function(x) x^2 closure function function
\+ builtin function function
\[ special function function

objects have class even if no formal class attribute (seen with attributes())

S3 Object System

What is S3?

  • S3 is R’s core object oriented implementation

  • S3 is the only OO system used in the base and stats packages, and it’s the most commonly used system in CRAN packages.

  • S3 is more flexible (read also: easier to break) than traditional object oriented programming you may have seen before (e.g. Java).

Working model: S3 is a system where we attach metadata to an object via the class attribute.

One of the most compelling reasons to use object oriented programming (OOP) is polymorphism. Polymorphism means that a developer can consider a function’s interface separately from its implementation.

S3 class specialization

x = c("A","B","A","C")
print( x )
[1] "A" "B" "A" "C"
print( factor(x) )
[1] A B A C
Levels: A B C
print( unclass( factor(x) ) ) # unclass strips the class attribute
[1] 1 2 1 3
attr(,"levels")
[1] "A" "B" "C"
print.default( factor(x) )
[1] 1 2 1 3

Other examples

mean
function (x, ...) 
UseMethod("mean")
<bytecode: 0x7fa019945990>
<environment: namespace:base>
t.test
function (x, ...) 
UseMethod("t.test")
<bytecode: 0x7fa04969c740>
<environment: namespace:stats>

Not all base functions use this approach,

sum
function (..., na.rm = FALSE)  .Primitive("sum")

Note

The ... (dot-dot-dot) argument means the function can take any number of additional arguments. This is called a “variable argument” and it is commonly used so you pass variables to additional nested functions. For example:

f1 =  function(y = 0, z = 0) {
  return(y + z)
}

f2 <- function(x, ...) {
  return(x + f1(...))
}

f2(x = 1, y = 2, z = 3)

Generics (what’s going on!?)

  • An S3 object behaves differently from its underlying base type whenever it’s passed to a generic function

The easiest way to tell if a function is a generic is to use sloop::ftype() and look for “generic” in the output, e.g.

library(sloop)
ftype(print)
[1] "S3"      "generic"

Fundamentally, the generic function dispatches to a specialized version of the function depending on the class of its input. UseMethod() tells a generic to look for the specialized version.

The syntax for specialized functions is: <generic>.<class>

Examples

We can see all the specialized versions of the generic using the methods() function.

methods("plot") |>
  head(n = 15)
 [1] "plot.acf"           "plot.data.frame"    "plot.decomposed.ts"
 [4] "plot.default"       "plot.dendrogram"    "plot.density"      
 [7] "plot.ecdf"          "plot.factor"        "plot.formula"      
[10] "plot.function"      "plot.hclust"        "plot.histogram"    
[13] "plot.HoltWinters"   "plot.isoreg"        "plot.lm"           

Methods of print

methods("print")
  [1] print.acf*                                          
  [2] print.AES*                                          
  [3] print.anova*                                        
  [4] print.aov*                                          
  [5] print.aovlist*                                      
  [6] print.ar*                                           
  [7] print.Arima*                                        
  [8] print.arima0*                                       
  [9] print.AsIs                                          
 [10] print.aspell*                                       
 [11] print.aspell_inspect_context*                       
 [12] print.bibentry*                                     
 [13] print.Bibtex*                                       
 [14] print.browseVignettes*                              
 [15] print.by                                            
 [16] print.changedFiles*                                 
 [17] print.check_bogus_return*                           
 [18] print.check_code_usage_in_package*                  
 [19] print.check_compiled_code*                          
 [20] print.check_demo_index*                             
 [21] print.check_depdef*                                 
 [22] print.check_details*                                
 [23] print.check_details_changes*                        
 [24] print.check_doi_db*                                 
 [25] print.check_dotInternal*                            
 [26] print.check_make_vars*                              
 [27] print.check_nonAPI_calls*                           
 [28] print.check_package_code_assign_to_globalenv*       
 [29] print.check_package_code_attach*                    
 [30] print.check_package_code_data_into_globalenv*       
 [31] print.check_package_code_startup_functions*         
 [32] print.check_package_code_syntax*                    
 [33] print.check_package_code_unload_functions*          
 [34] print.check_package_compact_datasets*               
 [35] print.check_package_CRAN_incoming*                  
 [36] print.check_package_datalist*                       
 [37] print.check_package_datasets*                       
 [38] print.check_package_depends*                        
 [39] print.check_package_description*                    
 [40] print.check_package_description_encoding*           
 [41] print.check_package_license*                        
 [42] print.check_packages_in_dir*                        
 [43] print.check_packages_used*                          
 [44] print.check_po_files*                               
 [45] print.check_pragmas*                                
 [46] print.check_Rd_line_widths*                         
 [47] print.check_Rd_metadata*                            
 [48] print.check_Rd_xrefs*                               
 [49] print.check_RegSym_calls*                           
 [50] print.check_S3_methods_needing_delayed_registration*
 [51] print.check_so_symbols*                             
 [52] print.check_T_and_F*                                
 [53] print.check_url_db*                                 
 [54] print.check_vignette_index*                         
 [55] print.checkDocFiles*                                
 [56] print.checkDocStyle*                                
 [57] print.checkFF*                                      
 [58] print.checkRd*                                      
 [59] print.checkRdContents*                              
 [60] print.checkReplaceFuns*                             
 [61] print.checkS3methods*                               
 [62] print.checkTnF*                                     
 [63] print.checkVignettes*                               
 [64] print.citation*                                     
 [65] print.cli_ansi_html_style*                          
 [66] print.cli_ansi_string*                              
 [67] print.cli_ansi_style*                               
 [68] print.cli_boxx*                                     
 [69] print.cli_diff_chr*                                 
 [70] print.cli_doc*                                      
 [71] print.cli_progress_demo*                            
 [72] print.cli_rule*                                     
 [73] print.cli_sitrep*                                   
 [74] print.cli_spark*                                    
 [75] print.cli_spinner*                                  
 [76] print.cli_tree*                                     
 [77] print.codoc*                                        
 [78] print.codocClasses*                                 
 [79] print.codocData*                                    
 [80] print.colorConverter*                               
 [81] print.compactPDF*                                   
 [82] print.condition                                     
 [83] print.connection                                    
 [84] print.CRAN_package_reverse_dependencies_and_views*  
 [85] print.data.frame                                    
 [86] print.Date                                          
 [87] print.default                                       
 [88] print.dendrogram*                                   
 [89] print.density*                                      
 [90] print.difftime                                      
 [91] print.dist*                                         
 [92] print.Dlist                                         
 [93] print.DLLInfo                                       
 [94] print.DLLInfoList                                   
 [95] print.DLLRegisteredRoutines                         
 [96] print.document_context*                             
 [97] print.document_position*                            
 [98] print.document_range*                               
 [99] print.document_selection*                           
[100] print.dummy_coef*                                   
[101] print.dummy_coef_list*                              
[102] print.ecdf*                                         
[103] print.eigen                                         
[104] print.factanal*                                     
[105] print.factor                                        
[106] print.family*                                       
[107] print.fileSnapshot*                                 
[108] print.findLineNumResult*                            
[109] print.formula*                                      
[110] print.fseq*                                         
[111] print.ftable*                                       
[112] print.function                                      
[113] print.getAnywhere*                                  
[114] print.glm*                                          
[115] print.hclust*                                       
[116] print.help_files_with_topic*                        
[117] print.hexmode                                       
[118] print.HoltWinters*                                  
[119] print.hsearch*                                      
[120] print.hsearch_db*                                   
[121] print.htest*                                        
[122] print.html*                                         
[123] print.html_dependency*                              
[124] print.htmltools.selector*                           
[125] print.htmltools.selector.list*                      
[126] print.infl*                                         
[127] print.integrate*                                    
[128] print.isoreg*                                       
[129] print.json*                                         
[130] print.key_missing*                                  
[131] print.kmeans*                                       
[132] print.knitr_kable*                                  
[133] print.Latex*                                        
[134] print.LaTeX*                                        
[135] print.libraryIQR                                    
[136] print.listof                                        
[137] print.lm*                                           
[138] print.loadings*                                     
[139] print.loess*                                        
[140] print.logLik*                                       
[141] print.ls_str*                                       
[142] print.medpolish*                                    
[143] print.method_table*                                 
[144] print.MethodsFunction*                              
[145] print.mtable*                                       
[146] print.NativeRoutineList                             
[147] print.news_db*                                      
[148] print.nls*                                          
[149] print.noquote                                       
[150] print.numeric_version                               
[151] print.object_size*                                  
[152] print.octmode                                       
[153] print.packageDescription*                           
[154] print.packageInfo                                   
[155] print.packageIQR*                                   
[156] print.packageStatus*                                
[157] print.pairwise.htest*                               
[158] print.person*                                       
[159] print.POSIXct                                       
[160] print.POSIXlt                                       
[161] print.power.htest*                                  
[162] print.ppr*                                          
[163] print.prcomp*                                       
[164] print.princomp*                                     
[165] print.proc_time                                     
[166] print.quosure*                                      
[167] print.quosures*                                     
[168] print.raster*                                       
[169] print.Rd*                                           
[170] print.recordedplot*                                 
[171] print.restart                                       
[172] print.RGBcolorConverter*                            
[173] print.rlang_box_done*                               
[174] print.rlang_box_splice*                             
[175] print.rlang_data_pronoun*                           
[176] print.rlang_dict*                                   
[177] print.rlang_dyn_array*                              
[178] print.rlang_envs*                                   
[179] print.rlang_error*                                  
[180] print.rlang_fake_data_pronoun*                      
[181] print.rlang_lambda_function*                        
[182] print.rlang_message*                                
[183] print.rlang_trace*                                  
[184] print.rlang_warning*                                
[185] print.rlang_zap*                                    
[186] print.rlang:::list_of_conditions*                   
[187] print.rle                                           
[188] print.rlib_bytes*                                   
[189] print.rlib_error_3_0*                               
[190] print.rlib_trace_3_0*                               
[191] print.roman*                                        
[192] print.scalar*                                       
[193] print.sessionInfo*                                  
[194] print.shiny.tag*                                    
[195] print.shiny.tag.env*                                
[196] print.shiny.tag.list*                               
[197] print.shiny.tag.query*                              
[198] print.simple.list                                   
[199] print.smooth.spline*                                
[200] print.socket*                                       
[201] print.srcfile                                       
[202] print.srcref                                        
[203] print.stepfun*                                      
[204] print.stl*                                          
[205] print.StructTS*                                     
[206] print.subdir_tests*                                 
[207] print.summarize_CRAN_check_status*                  
[208] print.summary.aov*                                  
[209] print.summary.aovlist*                              
[210] print.summary.ecdf*                                 
[211] print.summary.glm*                                  
[212] print.summary.lm*                                   
[213] print.summary.loess*                                
[214] print.summary.manova*                               
[215] print.summary.nls*                                  
[216] print.summary.packageStatus*                        
[217] print.summary.ppr*                                  
[218] print.summary.prcomp*                               
[219] print.summary.princomp*                             
[220] print.summary.table                                 
[221] print.summary.warnings                              
[222] print.summaryDefault                                
[223] print.table                                         
[224] print.tables_aov*                                   
[225] print.terms*                                        
[226] print.ts*                                           
[227] print.tskernel*                                     
[228] print.TukeyHSD*                                     
[229] print.tukeyline*                                    
[230] print.tukeysmooth*                                  
[231] print.undoc*                                        
[232] print.vignette*                                     
[233] print.warnings                                      
[234] print.xfun_raw_string*                              
[235] print.xfun_rename_seq*                              
[236] print.xfun_strict_list*                             
[237] print.xgettext*                                     
[238] print.xngettext*                                    
[239] print.xtabs*                                        
see '?methods' for accessing help and source code

Find the documentation

Write the name of the function to see its definition.

print.factor
function (x, quote = FALSE, max.levels = NULL, width = getOption("width"), 
    ...) 
{
    ord <- is.ordered(x)
    if (length(x) == 0L) 
        cat(if (ord) 
            "ordered"
        else "factor", "(0)\n", sep = "")
    else {
        xx <- character(length(x))
        xx[] <- as.character(x)
        keepAttrs <- setdiff(names(attributes(x)), c("levels", 
            "class"))
        attributes(xx)[keepAttrs] <- attributes(x)[keepAttrs]
        print(xx, quote = quote, ...)
    }
    maxl <- if (is.null(max.levels)) 
        TRUE
    else max.levels
    if (maxl) {
        n <- length(lev <- encodeString(levels(x), quote = ifelse(quote, 
            "\"", "")))
        colsep <- if (ord) 
            " < "
        else " "
        T0 <- "Levels: "
        if (is.logical(maxl)) 
            maxl <- {
                width <- width - (nchar(T0, "w") + 3L + 1L + 
                  3L)
                lenl <- cumsum(nchar(lev, "w") + nchar(colsep, 
                  "w"))
                if (n <= 1L || lenl[n] <= width) 
                  n
                else max(1L, which.max(lenl > width) - 1L)
            }
        drop <- n > maxl
        cat(if (drop) 
            paste(format(n), ""), T0, paste(if (drop) 
            c(lev[1L:max(1, maxl - 1)], "...", if (maxl > 1) lev[n])
        else lev, collapse = colsep), "\n", sep = "")
    }
    if (!isTRUE(val <- .valid.factor(x))) 
        warning(val)
    invisible(x)
}
<bytecode: 0x7fa01974f030>
<environment: namespace:base>

Missing print functions?

There is no method to print integers specifically:

print.integer
Error in eval(expr, envir, enclos): object 'print.integer' not found
  • When a method for a specific class is not found, the default will be called:
print.default
function (x, digits = NULL, quote = TRUE, na.print = NULL, print.gap = NULL, 
    right = FALSE, max = NULL, width = NULL, useSource = TRUE, 
    ...) 
{
    args <- pairlist(digits = digits, quote = quote, na.print = na.print, 
        print.gap = print.gap, right = right, max = max, width = width, 
        useSource = useSource, ...)
    missings <- c(missing(digits), missing(quote), missing(na.print), 
        missing(print.gap), missing(right), missing(max), missing(width), 
        missing(useSource))
    .Internal(print.default(x, args, missings))
}
<bytecode: 0x7fa01a9c3f50>
<environment: namespace:base>

What methods are specialized for a class?

methods(class = "factor")
 [1] [             [[            [[<-          [<-           all.equal    
 [6] as.character  as.data.frame as.Date       as.list       as.logical   
[11] as.POSIXlt    as.vector     c             coerce        droplevels   
[16] format        initialize    is.na<-       length<-      levels<-     
[21] Math          Ops           plot          print         relevel      
[26] relist        rep           show          slotsFromS3   summary      
[31] Summary       xtfrm        
see '?methods' for accessing help and source code

Make your own class and class-specific methods

Create an object of a new class

cloud = structure(c(1, 2, 3), # object called cloud with some values
              class = "first_class") # new class type
cloud
[1] 1 2 3
attr(,"class")
[1] "first_class"

Create a new method for first_class objects

print.first_class = function(x) {
  cat("Soldier, first class.\n")
  print.default(unclass(x))
}
print(cloud)
Soldier, first class.
[1] 1 2 3
class(cloud) = "double"
print(cloud)
[1] 1 2 3

Exercise 1

Create a class called “accounting”. If a numeric vector has this class, function print() should print the vector with a $ in front of each number and display values up to two decimals. Create a method for this class and test it on the accounting object expenses below

expenses = structure(
  c(500.5, 750, 200.3, 305.11),
  class = "accounting")

print(expenses)
[1] 500.50 750.00 200.30 305.11
attr(,"class")
[1] "accounting"

Hint

  • Use format(500, digits = 2, nsmall = 2) as a template to round to specific significant digits.
  • Use paste or paste0 to piece strings together.

Defining a new S3 Generic

shuffle = function(x) {
  UseMethod("shuffle")
}
shuffle.default = function(x) {
  stop("Class ", class(x), " is not supported by shuffle. \n", call. = FALSE)
}
shuffle.factor = function(f) {
  factor( sample(as.character(f)), levels = sample(levels(f)) )
}
shuffle.integer = function(x) {
  sample(x)
}
shuffle( 1:10 )
 [1]  1  6  8  5  7  3  4 10  2  9
shuffle( factor(c("A","B","C","A")) )
[1] B C A A
Levels: B A C
shuffle( c(1, 2, 3, 4, 5) )
Error: Class numeric is not supported by shuffle. 
shuffle( letters[1:5] )
Error: Class character is not supported by shuffle. 

Exercise 2

Below is an S3 method called report, it is designed to return a message about the type/mode/class of an object passed to it.

report = function(x) {
  UseMethod("report")
}
report.default = function(x) {
  "This class does not have a method defined."
}

Create an implementation for classes integer, double and numeric. The functions should print to the screen “I’m an integer”, “I’m a double”, and “I’m a numeric” respectively.

Next,

  • run report(1) and report(1L).
  • run rm("report.integer") and re-run the functions. What has changed?
  • look at class(1), mode(1), typeof(1). Does this surprise you?
  • Integers and doubles violate the typical class searching rule.

Some classes

Date class

Date vectors are built on top of double vectors. They have class “Date” and no other attributes:

today = Sys.Date()

typeof(today)
[1] "double"
attributes(today)
$class
[1] "Date"

The dawn of time

The date is measured in unix time, that is, the number of days that have passed since January 1st, 1970. Seconds are counted similarly, startin from the beginning of time, 1970 00:00:00 UTC.

Examples

as.double(today)
[1] 19385
unclass(today)
[1] 19385
date = as.Date("1970-01-11")

unclass(date)
[1] 10

Date-times

R offers two ways to store date-time info. POSIXct, and POSIXlt

  • POSIX stands for “Portable Operating Systems Interface” X (“across”) platforms

    • ct: calendar time
    • lt: local time

ct is the simplest, is built on top of an atomic vector, and is most appropriate for use in data frames.

Exercise 3

  • what class is today?
  • how can you find all the different functions that take the class of today?
  • how many days until the end of class? Note: the last day of class is April 26. Use difftime

Matrices

R supports the creation of 2d data structures (rows and columns) of atomic vector types. Generally these are formed via a call to matrix().

matrix(1:4, nrow=2, ncol=2)
     [,1] [,2]
[1,]    1    3
[2,]    2    4
matrix(LETTERS[1:6], 2)
     [,1] [,2] [,3]
[1,] "A"  "C"  "E" 
[2,] "B"  "D"  "F" 

Matrices in R use column major ordering (data is sorted in column order not row order).

(m = matrix(1:6, nrow=2, ncol=3))
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6
c(m)
[1] 1 2 3 4 5 6
(n = matrix(1:6, nrow=3,
            ncol=2, byrow=TRUE))
     [,1] [,2]
[1,]    1    2
[2,]    3    4
[3,]    5    6
c(n)
[1] 1 3 5 2 4 6

Matrix structure

Matrices (and arrays) are just atomic vectors with a dim attribute attached (they do not have a class attribute, but they do have a class).

m = matrix(1:4, ncol=2, nrow=2)
typeof(m)
[1] "integer"
mode(m)
[1] "numeric"
class(m)
[1] "matrix" "array" 
attributes(m)
$dim
[1] 2 2
n = letters[1:6]
dim(n) = c(2L, 3L)
n
     [,1] [,2] [,3]
[1,] "a"  "c"  "e" 
[2,] "b"  "d"  "f" 
class(n)
[1] "matrix" "array" 

Arrays

Arrays are just an \(n\)-dimensional extension of matrices and are defined by adding the appropriate dimension sizes.

array(1:8, dim = c(2,2,2))
, , 1

     [,1] [,2]
[1,]    1    3
[2,]    2    4

, , 2

     [,1] [,2]
[1,]    5    7
[2,]    6    8
array(letters[1:6], dim = c(2,1,3))
, , 1

     [,1]
[1,] "a" 
[2,] "b" 

, , 2

     [,1]
[1,] "c" 
[2,] "d" 

, , 3

     [,1]
[1,] "e" 
[2,] "f" 

Data frames

Data frames are built on top of lists with attributes: names, row.names, and class. Here the class is data.frame.

typeof(cars)
[1] "list"
attributes(cars)
$names
[1] "speed" "dist" 

$class
[1] "data.frame"

$row.names
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
[26] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
str(unclass(cars))
List of 2
 $ speed: num [1:50] 4 4 7 7 8 9 10 10 10 11 ...
 $ dist : num [1:50] 2 10 4 22 16 10 18 26 34 17 ...
 - attr(*, "row.names")= int [1:50] 1 2 3 4 5 6 7 8 9 10 ...

A data frame is how R handles heterogeneous tabular data (i.e. rows and columns) and is one of the most commonly used data structure in R.

Build your own data frame

df = list(x = 1:3, y = c("a", "b", "c"), z = c(TRUE, TRUE, TRUE))

attr(df,"class") = "data.frame"
attr(df,"row.names") = 1:3
df
  x y    z
1 1 a TRUE
2 2 b TRUE
3 3 c TRUE
str(df)
'data.frame':   3 obs. of  3 variables:
 $ x: int  1 2 3
 $ y: chr  "a" "b" "c"
 $ z: logi  TRUE TRUE TRUE
is.data.frame(df)
[1] TRUE