class: center, middle, inverse, title-slide # Advanced R: Chapters 12-13 ## Daryn Ramsden ### thisisdaryn at gmail dot com ### last updated: 2020-06-19 --- ## Object-Oriented Programming **Tautology**: -- Object-oriented programming is a programming paradigm centered around objects -- **What's an object?** -- Objects are collections of data and methods * every object has a type (class) * the class of the object determines: * its attributes * how you can interact with the object -- **The key idea**: the nature of the object tells you how you can interact with the object i.e. what functions you can use --- ### What makes OOP useful? The main reason: -- **Polymorphism** -- Polymorphism allows developer to think about a function's interface separately. --- ### What's the deal with OOP in R? -- It's complicated. Mostly because there's a lot of different ways to do it. -- Different OOP systems in R include: * S3 * R6 * S4 -- **What's meant by an OOP system?** -- - a collection of language features that allow one to program in an object-oriented fashion. --- ## Why is Chapter 12 necessary? In R the term *object* gets used in two different ways: 1. Everything is an object 2. R has object-oriented systems: S3, R6, S4 -- **Main thing**: not every object is object-oriented ![Diagram showing OO objects as base objects as distinct subsets of R objects](images/object_classification.png) --- ## Do base types constitute an OOP system? -- **No**: -- * functions that act differently on different types are handled in switch statements in C -- * new types are: * impossible for application developers to create * rarely created by R-core --- ## The difference between base and OOP types OO objects have a *class* attribute Telling the difference between base --- ## The different base types 23 base types listed in Chapter 12: * **Vectors**: `NULL` (NILSXP), `logical` (LGLSXP), `integer` (INTSXP), `double` (REALSXP), `complex` (CPLXSXP), `character` (STRSXP), `list` (VECSXP), `raw` (RAWSXP) * **Functions**: `closure` (regular R functions, CLOSXP), `special` (internal functions, SPECIALSXP), `builtin` (primitive functions, BUILTINSXP) * **Environments**: `environment` (ENVSXP) * **S4**: `S4` (S4SXP) * **Language Components**: `symbol` (aka name, SYMSXP), `language` (usually called calls, LANGSXP), `pairlist` (used for function arguments, LISTSXP), `expression` (EXPRSXP) * **Esoteric**: `externalptr` (EXTPTRSXP), `weakref` (WEAKREFSXP), `bytecode` (BCODESXP), `promise` (PROMSXP), `...` (DOTSXP), `any` (ANYSXP). --- ## Other base types: CRAN's [*R Internals Guide*](https://cran.r-project.org/doc/manuals/r-release/R-ints.html#SEXPTYPEs) also lists: * 2 previously used base types for internal factors and ordered factors have been withdrawn * R internally uses a type CHARSXP to represent strings --- ## The `numeric` base type 1. Sometimes used to mean the `double` type 2. In S3 and S4, can be used to mean either `integer` or `double` 3. `is.numeric` is used to identify objects that behave like numbers. (As opposed to whether or not their type is integer.) --- ## Chapter 13: S3 S3 is R's oldest OO system: * minimalist * very flexible * most-commonly used system in CRAN packages * the only OOP system used in `base` and `stats` packages * a lot different to most object-oriented systems in widely-used languages --- ## S3 objects: the basics What do you need for an S3 object? -- * a base type with a *class* attribute -- * that's it -- There are no checks for correctness in S3 --- ### Looking at the factor class ```r f <- factor(c("a", "b", "c")) typeof(f) ``` ``` [1] "integer" ``` ```r attributes(f) ``` ``` $levels [1] "a" "b" "c" $class [1] "factor" ``` --- ### Using `unclass` You can get the base type of an S3 object using `unclass` ```r unclass(f) ``` ``` [1] 1 2 3 attr(,"levels") [1] "a" "b" "c" ``` --- ### Can I create my own class? --- ## Object styles In the wild S3 objects can be said to be of different styles: 1. **Vector style** objects - based on an underlying vector - may have other attributes - have key property that `length(x)` represents the number of observations in the vector 2. **Record style** objects - based on vectors of equal length 3. **data frames** - based on vectors of equal length - conceptually 2-dimensional - number of observations is the number of rows not the length 4. **scalar** objects: use a list to represent a single thing Chapter places emphasis on illustrating concepts using vector styled objects. --- ## A record style object: POSIXlt A `POSIXlt` object consists of 11 vectors: *sec*, *min*, *hour*, *mday*, *mon*, *year*, *wday*, *yday*, *isdst*, *zone*, *gmtoff* ```r x <- as.POSIXlt(ISOdatetime(2020, 1, 1, 0, 0, 1:3)) unclass(x)[1:6] ``` ``` $sec [1] 1 2 3 $min [1] 0 0 0 $hour [1] 0 0 0 $mday [1] 1 1 1 $mon [1] 0 0 0 $year [1] 120 120 120 ``` --- ## A record style object: POSIXlt ```r unclass(x)[7:11] ``` ``` $wday [1] 3 3 3 $yday [1] 0 0 0 $isdst [1] 0 0 0 $zone [1] "AST" "AST" "AST" $gmtoff [1] -14400 -14400 -14400 ``` --- ## A record style object: POSIXlt The attributes of a `POSIXlt` object ```r attributes(x) ``` ``` $names [1] "sec" "min" "hour" "mday" "mon" "year" "wday" "yday" [9] "isdst" "zone" "gmtoff" $class [1] "POSIXlt" "POSIXt" $tzone [1] "" "AST" " " ``` --- ## A scalar object type: `lm` ```r mod <- lm(mpg ~ wt, data = mtcars) ``` ![Screenshot of an lm object viewed in the RStudio Object browser](images/lm_model.png) --- ## Recommended framework for creating S3 objects Advanced R recommends a 3-level structure for defining S3 classes: * **constructor**, `new_myclass()`: efficiently creates new objects with the correct structure. * **validator**, `validate_myclass()`: performs more computationally expensive checks to ensure that the object has correct values. * **helper**, `myclass()`: provides a convenient way for others to create objects of your class. --- ## Constructor guidelines * one argument for the object type, one argument for each attribute * types of each should be checked in the constructor --- ### Example: constructor for a factor class ```r new_factor <- function(x = integer(), levels = character()) { stopifnot(is.integer(x)) stopifnot(is.character(levels)) structure( x, levels = levels, class = "factor" ) } ``` --- ### Example: validator for factor class ```r validate_factor <- function(x) { values <- unclass(x) levels <- attr(x, "levels") if (!all(!is.na(values) & values > 0)) { stop( "All `x` values must be non-missing and greater than zero", call. = FALSE ) } if (length(levels) < max(values)) { stop( "There must be at least as many `levels` as possible values in `x`", call. = FALSE ) } x } ``` --- ### Example: helper for a factor class ```r factor <- function(x = character(), levels = unique(x)) { ind <- match(x, levels) validate_factor(new_factor(ind, levels)) } ``` --- ## Interacting with S3 objects: To interact with an S3 object, you have to use functions. There are two types of functions involved: 1. **generic** functions: * have well-defined interfaces * serve as intermediaries * choose which specific **method** is called based on the class of the input 2. **methods**: * are written to be used with a specific class --- ## Example of a generic function: `print` We can use `sloop::s3_methods_generic` to see the methods associated with a generic function. ```r sloop::s3_methods_generic("print") ``` <div data-pagedtable="false"> <script data-pagedtable-source type="application/json"> {"columns":[{"label":["generic"],"name":[1],"type":["chr"],"align":["left"]},{"label":["class"],"name":[2],"type":["chr"],"align":["left"]},{"label":["visible"],"name":[3],"type":["lgl"],"align":["right"]},{"label":["source"],"name":[4],"type":["chr"],"align":["left"]}],"data":[{"1":"print","2":"acf","3":"FALSE","4":"registered S3method"},{"1":"print","2":"AES","3":"FALSE","4":"registered S3method"},{"1":"print","2":"anova","3":"FALSE","4":"registered S3method"},{"1":"print","2":"aov","3":"FALSE","4":"registered S3method"},{"1":"print","2":"aovlist","3":"FALSE","4":"registered S3method"},{"1":"print","2":"ar","3":"FALSE","4":"registered S3method"},{"1":"print","2":"Arima","3":"FALSE","4":"registered S3method"},{"1":"print","2":"arima0","3":"FALSE","4":"registered S3method"},{"1":"print","2":"AsIs","3":"TRUE","4":"base"},{"1":"print","2":"aspell","3":"FALSE","4":"registered S3method"},{"1":"print","2":"aspell_inspect_context","3":"FALSE","4":"registered S3method"},{"1":"print","2":"bibentry","3":"FALSE","4":"registered S3method"},{"1":"print","2":"Bibtex","3":"FALSE","4":"registered S3method"},{"1":"print","2":"browseVignettes","3":"FALSE","4":"registered S3method"},{"1":"print","2":"by","3":"TRUE","4":"base"},{"1":"print","2":"bytes","3":"FALSE","4":"registered S3method"},{"1":"print","2":"changedFiles","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_code_usage_in_package","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_compiled_code","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_demo_index","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_depdef","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_details","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_details_changes","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_doi_db","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_dotInternal","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_make_vars","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_nonAPI_calls","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_code_assign_to_globalenv","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_code_attach","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_code_data_into_globalenv","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_code_startup_functions","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_code_syntax","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_code_unload_functions","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_compact_datasets","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_CRAN_incoming","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_datalist","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_datasets","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_depends","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_description","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_description_encoding","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_package_license","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_packages_in_dir","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_packages_used","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_po_files","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_pragmas","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_Rd_contents","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_Rd_line_widths","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_Rd_metadata","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_Rd_xrefs","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_RegSym_calls","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_S3_methods_needing_delayed_registration","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_so_symbols","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_T_and_F","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_url_db","3":"FALSE","4":"registered S3method"},{"1":"print","2":"check_vignette_index","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkDocFiles","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkDocStyle","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkFF","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkRd","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkReplaceFuns","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkS3methods","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkTnF","3":"FALSE","4":"registered S3method"},{"1":"print","2":"checkVignettes","3":"FALSE","4":"registered S3method"},{"1":"print","2":"citation","3":"FALSE","4":"registered S3method"},{"1":"print","2":"codoc","3":"FALSE","4":"registered S3method"},{"1":"print","2":"codocClasses","3":"FALSE","4":"registered S3method"},{"1":"print","2":"codocData","3":"FALSE","4":"registered S3method"},{"1":"print","2":"colorConverter","3":"FALSE","4":"registered S3method"},{"1":"print","2":"compactPDF","3":"FALSE","4":"registered S3method"},{"1":"print","2":"condition","3":"TRUE","4":"base"},{"1":"print","2":"connection","3":"TRUE","4":"base"},{"1":"print","2":"CRAN_package_reverse_dependencies_and_views","3":"FALSE","4":"registered S3method"},{"1":"print","2":"data.frame","3":"TRUE","4":"base"},{"1":"print","2":"Date","3":"TRUE","4":"base"},{"1":"print","2":"default","3":"TRUE","4":"base"},{"1":"print","2":"dendrogram","3":"FALSE","4":"registered S3method"},{"1":"print","2":"density","3":"FALSE","4":"registered S3method"},{"1":"print","2":"difftime","3":"TRUE","4":"base"},{"1":"print","2":"dist","3":"FALSE","4":"registered S3method"},{"1":"print","2":"Dlist","3":"TRUE","4":"base"},{"1":"print","2":"DLLInfo","3":"TRUE","4":"base"},{"1":"print","2":"DLLInfoList","3":"TRUE","4":"base"},{"1":"print","2":"DLLRegisteredRoutines","3":"TRUE","4":"base"},{"1":"print","2":"dummy_coef","3":"FALSE","4":"registered S3method"},{"1":"print","2":"dummy_coef_list","3":"FALSE","4":"registered S3method"},{"1":"print","2":"ecdf","3":"FALSE","4":"registered S3method"},{"1":"print","2":"eigen","3":"TRUE","4":"base"},{"1":"print","2":"factanal","3":"FALSE","4":"registered S3method"},{"1":"print","2":"factor","3":"TRUE","4":"base"},{"1":"print","2":"family","3":"FALSE","4":"registered S3method"},{"1":"print","2":"fileSnapshot","3":"FALSE","4":"registered S3method"},{"1":"print","2":"findLineNumResult","3":"FALSE","4":"registered S3method"},{"1":"print","2":"formula","3":"FALSE","4":"registered S3method"},{"1":"print","2":"frame","3":"FALSE","4":"registered S3method"},{"1":"print","2":"fseq","3":"FALSE","4":"registered S3method"},{"1":"print","2":"ftable","3":"FALSE","4":"registered S3method"},{"1":"print","2":"function","3":"TRUE","4":"base"},{"1":"print","2":"getAnywhere","3":"FALSE","4":"registered S3method"},{"1":"print","2":"glm","3":"FALSE","4":"registered S3method"},{"1":"print","2":"hclust","3":"FALSE","4":"registered S3method"},{"1":"print","2":"help_files_with_topic","3":"FALSE","4":"registered S3method"},{"1":"print","2":"hexmode","3":"TRUE","4":"base"},{"1":"print","2":"HoltWinters","3":"FALSE","4":"registered S3method"},{"1":"print","2":"hsearch","3":"FALSE","4":"registered S3method"},{"1":"print","2":"hsearch_db","3":"FALSE","4":"registered S3method"},{"1":"print","2":"htest","3":"FALSE","4":"registered S3method"},{"1":"print","2":"html","3":"FALSE","4":"registered S3method"},{"1":"print","2":"html_dependency","3":"FALSE","4":"registered S3method"},{"1":"print","2":"infl","3":"FALSE","4":"registered S3method"},{"1":"print","2":"integrate","3":"FALSE","4":"registered S3method"},{"1":"print","2":"isoreg","3":"FALSE","4":"registered S3method"},{"1":"print","2":"kmeans","3":"FALSE","4":"registered S3method"},{"1":"print","2":"knitr_kable","3":"FALSE","4":"registered S3method"},{"1":"print","2":"Latex","3":"FALSE","4":"registered S3method"},{"1":"print","2":"LaTeX","3":"FALSE","4":"registered S3method"},{"1":"print","2":"libraryIQR","3":"TRUE","4":"base"},{"1":"print","2":"listof","3":"TRUE","4":"base"},{"1":"print","2":"lm","3":"FALSE","4":"registered S3method"},{"1":"print","2":"loadings","3":"FALSE","4":"registered S3method"},{"1":"print","2":"loess","3":"FALSE","4":"registered S3method"},{"1":"print","2":"logLik","3":"FALSE","4":"registered S3method"},{"1":"print","2":"ls_str","3":"FALSE","4":"registered S3method"},{"1":"print","2":"medpolish","3":"FALSE","4":"registered S3method"},{"1":"print","2":"method_table","3":"FALSE","4":"registered S3method"},{"1":"print","2":"MethodsFunction","3":"FALSE","4":"registered S3method"},{"1":"print","2":"mtable","3":"FALSE","4":"registered S3method"},{"1":"print","2":"NativeRoutineList","3":"TRUE","4":"base"},{"1":"print","2":"news_db","3":"FALSE","4":"registered S3method"},{"1":"print","2":"nls","3":"FALSE","4":"registered S3method"},{"1":"print","2":"noquote","3":"TRUE","4":"base"},{"1":"print","2":"numeric_version","3":"TRUE","4":"base"},{"1":"print","2":"object_size","3":"FALSE","4":"registered S3method"},{"1":"print","2":"octmode","3":"TRUE","4":"base"},{"1":"print","2":"packageDescription","3":"FALSE","4":"registered S3method"},{"1":"print","2":"packageInfo","3":"TRUE","4":"base"},{"1":"print","2":"packageIQR","3":"FALSE","4":"registered S3method"},{"1":"print","2":"packageStatus","3":"FALSE","4":"registered S3method"},{"1":"print","2":"pairwise.htest","3":"FALSE","4":"registered S3method"},{"1":"print","2":"person","3":"FALSE","4":"registered S3method"},{"1":"print","2":"POSIXct","3":"TRUE","4":"base"},{"1":"print","2":"POSIXlt","3":"TRUE","4":"base"},{"1":"print","2":"power.htest","3":"FALSE","4":"registered S3method"},{"1":"print","2":"ppr","3":"FALSE","4":"registered S3method"},{"1":"print","2":"prcomp","3":"FALSE","4":"registered S3method"},{"1":"print","2":"princomp","3":"FALSE","4":"registered S3method"},{"1":"print","2":"proc_time","3":"TRUE","4":"base"},{"1":"print","2":"quosure","3":"FALSE","4":"registered S3method"},{"1":"print","2":"quosures","3":"FALSE","4":"registered S3method"},{"1":"print","2":"raster","3":"FALSE","4":"registered S3method"},{"1":"print","2":"Rcpp_stack_trace","3":"FALSE","4":"registered S3method"},{"1":"print","2":"Rd","3":"FALSE","4":"registered S3method"},{"1":"print","2":"recordedplot","3":"FALSE","4":"registered S3method"},{"1":"print","2":"restart","3":"TRUE","4":"base"},{"1":"print","2":"RGBcolorConverter","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_box_done","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_box_splice","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_data_pronoun","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_envs","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_error","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_fake_data_pronoun","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_lambda_function","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_trace","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rlang_zap","3":"FALSE","4":"registered S3method"},{"1":"print","2":"rle","3":"TRUE","4":"base"},{"1":"print","2":"roman","3":"FALSE","4":"registered S3method"},{"1":"print","2":"sessionInfo","3":"FALSE","4":"registered S3method"},{"1":"print","2":"shiny.tag","3":"FALSE","4":"registered S3method"},{"1":"print","2":"shiny.tag.list","3":"FALSE","4":"registered S3method"},{"1":"print","2":"simple.list","3":"TRUE","4":"base"},{"1":"print","2":"smooth.spline","3":"FALSE","4":"registered S3method"},{"1":"print","2":"socket","3":"FALSE","4":"registered S3method"},{"1":"print","2":"srcfile","3":"TRUE","4":"base"},{"1":"print","2":"srcref","3":"TRUE","4":"base"},{"1":"print","2":"stepfun","3":"FALSE","4":"registered S3method"},{"1":"print","2":"stl","3":"FALSE","4":"registered S3method"},{"1":"print","2":"StructTS","3":"FALSE","4":"registered S3method"},{"1":"print","2":"subdir_tests","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summarize_CRAN_check_status","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.aov","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.aovlist","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.ecdf","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.glm","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.lm","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.loess","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.manova","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.nls","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.packageStatus","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.ppr","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.prcomp","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.princomp","3":"FALSE","4":"registered S3method"},{"1":"print","2":"summary.table","3":"TRUE","4":"base"},{"1":"print","2":"summary.warnings","3":"TRUE","4":"base"},{"1":"print","2":"summaryDefault","3":"TRUE","4":"base"},{"1":"print","2":"table","3":"TRUE","4":"base"},{"1":"print","2":"tables_aov","3":"FALSE","4":"registered S3method"},{"1":"print","2":"terms","3":"FALSE","4":"registered S3method"},{"1":"print","2":"ts","3":"FALSE","4":"registered S3method"},{"1":"print","2":"tskernel","3":"FALSE","4":"registered S3method"},{"1":"print","2":"TukeyHSD","3":"FALSE","4":"registered S3method"},{"1":"print","2":"tukeyline","3":"FALSE","4":"registered S3method"},{"1":"print","2":"tukeysmooth","3":"FALSE","4":"registered S3method"},{"1":"print","2":"undoc","3":"FALSE","4":"registered S3method"},{"1":"print","2":"vignette","3":"FALSE","4":"registered S3method"},{"1":"print","2":"warnings","3":"TRUE","4":"base"},{"1":"print","2":"xfun_raw_string","3":"FALSE","4":"registered S3method"},{"1":"print","2":"xfun_strict_list","3":"FALSE","4":"registered S3method"},{"1":"print","2":"xgettext","3":"FALSE","4":"registered S3method"},{"1":"print","2":"xngettext","3":"FALSE","4":"registered S3method"},{"1":"print","2":"xtabs","3":"FALSE","4":"registered S3method"}],"options":{"columns":{"min":{},"max":[10]},"rows":{"min":[8],"max":[8]},"pages":{}}} </script> </div> We can see that there are many methods defined that can potentially be called when the generic `print` function is called. --- ## Determining which method was used `sloop::s3_dispatch` ```r library(sloop) x <- matrix(1:10, nrow = 2) s3_dispatch(mean(x)) ``` ``` mean.matrix mean.integer mean.numeric => mean.default ``` ```r s3_dispatch(print(ordered("x"))) ``` ``` print.ordered => print.factor * print.default ``` ```r s3_dispatch(print(Sys.time())) ``` ``` => print.POSIXct print.POSIXt * print.default ``` --- ### Writing your own methods If you want to write your own method, there are 2 cases: 1. There's a pre-existing generic function * create a new method `generic.class` 2. There is not a pre-existing generic function. 1. Create a new generic 2. Create a method --- ## Creating your own method for a pre-existing generic --- ### Creating your own generic ```r my_new_generic <- function(x) { UseMethod("my_new_generic") } ``` --- ## How `UseMethod` works `UseMethod` : 1. creates a vector of method names, `paste0("generic", ".", c(class(x), "default"))` 2. looks for each member of the vector in turn Example: ```r x <- Sys.time() class(x) ``` ``` [1] "POSIXct" "POSIXt" ``` ```r s3_dispatch(sum(Sys.time())) ``` ``` sum.POSIXct sum.POSIXt sum.default => Summary.POSIXct Summary.POSIXt Summary.default -> sum (internal) ``` --- ## Inheritance in S3 * the *class* attribute can be a vector * if a method is not found in the first item in the *class* vector, R will look for a method for the second and so on ... * a method can delegate work by calling `NextMethod`. `s3_dispatch` reports delegation with `->` --- ### Example of S3 inheritance First, create a vector of the class *ordered* ```r my_vector <- ordered(c("x", "y")) class(my_vector) ``` ``` [1] "ordered" "factor" ``` *ordered* is a subclass of *factor* -- Now subset the created vector -- ```r s3_dispatch(my_vector[1]) ``` ``` [.ordered => [.factor [.default -> [ (internal) ``` `[.ordered` was not available, so R moved on to `[.factor` which then delegated to `[` --- ### On subclasses and superclasses * S3 imposes no constraints on the relationship between sub and superclasses * Recommended practice * The base type of the subclass should be that same as the superclass. * The attributes of the subclass should be a superset of the attributes of the superclass. --- ## Dispatch Details There are a few situations where method dispatch gets weird: * internal generics * group generics * double dispatch --- ### S3 dispatch with base objects The *class* attribute of a base object does not uniquely determine the method called ```r x1 <- 1:5 class(x1) ``` ``` [1] "integer" ``` ```r s3_dispatch(mean(x1)) ``` ``` mean.integer mean.numeric => mean.default ``` ```r x2 <- structure(x1, class = "integer") class(x2) ``` ``` [1] "integer" ``` ```r s3_dispatch(mean(x2)) ``` ``` mean.integer => mean.default ``` --- ### What actually happened? Dispatch is actually done using the implicit object. The implicit object is based on: 1. The string “array” or “matrix” if the object has dimensions 2. The result of `typeof()` with a few minor tweaks 3. The string “numeric” if object is “integer” or “double” We can use `sloop::s3_class` to get the implicit object type ```r s3_class(x1) ``` ``` [1] "integer" "numeric" ``` ```r s3_class(x2) ``` ``` [1] "integer" ``` --- ### Internal Generics Some base functions, like `[`, `sum()`, and `cbind()`, are called internal generics * they don’t call `UseMethod()` * instead call the C functions `DispatchGroup()` or `DispatchOrEval()` ```r s3_dispatch(Sys.time()[1]) ``` ``` => [.POSIXct [.POSIXt [.default -> [ (internal) ``` --- ### Group Generics There are 4 group generics: * **Math**: `abs`, `sign`, `sqrt`, `floor`, `cos`, `sin`, `log`, etc * **Ops**: `+`, `-`, `*`, `/`, `^`, `%%`, `%/%`, `&`, `|`, `!`, `==`, `!=`, `<`, `<=`, `>=`, and `>`. * **Summary**: `all`, `any`, `sum`, `prod`, `min`, `max`, and `range`. * **Complex**: `Arg`, `Conj`, `Im`, `Mod`, `Re` -- My understanding: you write `Math.class` (or `Ops.class`, `Summary.class`, or `Complex.class`) and it becomes a candidate if any of the group members gets called on your class. --- ### Some facts about Group Generics * Defining a single group generic for your class overrides the default behaviour for all of the members of the group. * Most group generics involve a call to NextMethod() ```r Math.difftime <- function(x, ...) { new_difftime(NextMethod(), units = attr(x, "units")) } ``` --- ### Double dispatch **double dispatch**: a special dispatch procedure required by members of the Ops group because: * Many of the functions in the Ops groups are binary operators * the method called should make sense considering both operands * many operators should be commutative Example: ```r date <- as.Date("2017-01-01") integer <- 1L ``` ```r date + integer ``` ``` [1] "2017-01-02" ``` ```r integer + date ``` ``` [1] "2017-01-02" ``` --- ### Double dispatch details Look up the methods for each operator. There are 3 cases: 1. The methods are the same: use that method 2. The methods are different: use the internal method with a warning. 3. One method is internal: use the other method. --- ## The End