diff --git a/16_Trade-offs.Rmd b/16_Trade-offs.Rmd index 83173cb..d6bf0de 100644 --- a/16_Trade-offs.Rmd +++ b/16_Trade-offs.Rmd @@ -1,372 +1,394 @@ -# Trade-offs - -**Learning objectives:** - -- Understand the Trade-offs between S3, R6 and S4 - -- Brief intro to S7 (the object system formerly known as R7) - - -## Introduction - -* We have three OOP systems introduced so far (S3, S4, R6) - -* At the current time (pre - S7?) Hadley recommends use S3 by default: It's simple and widely used throughout base R and CRAN. - -* If you have experience in other languages, *Resist* the temptation to use R6 even though it will feel more familiar! - - -## S4 versus S3 - -**Which functional object system to use, S4 or S3? ** - -- **S3** is a simple and flexible system. - - - Good for small teams who need flexibility and immediate payoffs. - - - Commonly used throughout base R and CRAN - - - Flexibility can cause problems, more complex systems might require formal conventions - - -- **S4** is a more formal, strict system. - - - Good for large projects and large teams - - - Used by Bioconductor project - - - Requires significant up front investment in design, but payoff is a robust system that enforces conventions. - - - S4 Documentation is challenging to use. - - - -## R6 versus S3 - -**R6** is built on **encapsulated objects**, rather than generic functions. - - -**Big differences: general trade-offs** - -```{r 16-Trade-offs-4, echo=FALSE,fig.align='center'} -knitr::include_graphics("images/16-trade-offs.png") -``` - -## Namespacing {-} - -**Where methods are found?** - -- in S3, **Generic functions** are **global** and live in the **global namespace** - - - Advantage: Uniform API: `summary`, `print`, `predict` etc. - - - Disadvantage: Must be careful about creating new methods! Homonyms must be avoided, don't define `plot(bank_heist)` - - -- in R6, **Encapsulated methods** are **local**: objects with a **scope** - - - Advantage: No problems with homonyms: meaning of `bank_heist$plot()` is clear and unambiguous. - - - Disadvantage: Lack of a uniform API, except by convention. - - -## Threading state {-} - - -In S3 the challenge is to return a value and modify the object. - - -```{r} -new_stack <- function(items = list()) { - structure(list(items = items), class = "stack") -} - -push <- function(x, y) { - x$items <- c(x$items, list(y)) - x -} -``` - -No problem with that, but what about when we want to pop a value? We need to return two things. - -```{r} -pop <- function(x) { - n <- length(x$items) - - item <- x$items[[n]] - x$items <- x$items[-n] - - list(item = item, x = x) -} -``` - -The usage is a bit awkward: - -```{r} -s <- new_stack() -s <- push(s, 10) -s <- push(s, 20) - -out <- pop(s) -# Update state: -s <- out$x - -print(out$item) -``` - - -In python and other languages we have structured binding to make this less awkward. R has the {zeallot} package. For more, see this vignette: - -```{r 16-Trade-offs-5, eval=FALSE} -vignette('unpacking-assignment') -``` - -However, this is all easier in R6 due to the reference semantics! - -```{r} -Stack <- R6::R6Class("Stack", list( - items = list(), - push = function(x) { - self$items <- c(self$items, x) - invisible(self) - }, - pop = function() { - item <- self$items[[self$length()]] - self$items <- self$items[-self$length()] - item - }, - length = function() { - length(self$items) - } -)) - -s <- Stack$new() -s$push(10) -s$push(20) -s$pop() -``` - - -## Method chaining {-} - -Useful to compose functions from left-to-right. - -Use of the operators: - -- S3: `%>%` - -- R6: `$` - -```{r} -s$push(44)$push(32)$pop() -``` - - -## Umm... what about S7 ? {-} - -```{r standards, echo = FALSE, fig.cap = "https://xkcd.com/927/"} - -knitr::include_graphics("https://imgs.xkcd.com/comics/standards_2x.png") - -``` - -### Primary references: {-} - -* Docs: https://rconsortium.github.io/OOP-WG/ - -* Talk by Hadley Wickham https://www.youtube.com/watch?v=P3FxCvSueag - -## S7 briefly {-} - -* S7 is a 'better' version of S3 with some of the 'strictness' of S4. - -``` -"A little bit more complex then S3, with almost all of the features, -all of the payoff of S4" - rstudio conf 2022, Hadley Wickham -``` - -* Compatible with S3: S7 objects are S3 objects! Can even extend an S3 object with S7 - -* Somewhat compatible with S4, see [compatability vignette](https://rconsortium.github.io/OOP-WG/articles/compatibility.html) for details. - -* Helpful error messages! - -* Note that it was previously called R7, but it was changed to "S7" to better reflect that it is functional not encapsulated! - -## Abbreviated introduction based on the vignette {-} - -To install: -```{r, eval=FALSE} -# install.packages("remotes") -remotes::install_github("rconsortium/OOP-WG") -``` - - -```{r, eval=FALSE} -library(S7) -dog <- new_class("dog", properties = list( - name = class_character, - age = class_numeric -)) -dog - - -#> -#> @ name : dog -#> @ parent: -#> @ properties: -#> $ name: -#> $ age : or -``` - -Note the 'class_character', these are S7 classes corresponding to the base classes. - -Now to use it: - -```{r, eval = FALSE} -lola <- dog(name = "Lola", age = 11) -lola - -#> -#> @ name: chr "Lola" -#> @ age : num 11 -``` - -Properties can be set / read with '@', with automatic validation ('safety rails') based on the type! - -```{r, eval = FALSE} - -lola@age <- 12 -lola@age - -#> 12 - -lola@age <- "twelve" - -#> Error: @age must be or , not - -``` - -Note thehelpful error message! - -Like S3 (and S4) S7 has generics, implemented with `new_generic` and `method` for particular methods: - -```{r, eval = FALSE} -speak <- new_generic("speak", "x") - -method(speak, dog) <- function(x) { - "Woof" -} - -speak(lola) - -#> [1] "Woof" -``` - -If we have another class, we can implement the generic for that too: - -```{r, eval = FALSE} -cat <- new_class("cat", properties = list( - name = class_character, - age = class_double -)) -method(speak, cat) <- function(x) { - "Meow" -} - -fluffy <- cat(name = "Fluffy", age = 5) -speak(fluffy) - -#> [1] "Meow" -``` - -Helpful messages: - -```{r, eval = FALSE} -speak - -#> speak(x, ...) with 2 methods: -#> 1: method(speak, cat) -#> 2: method(speak, dog) -``` - - -"most usage of S7 with S3 will just work" - -```{r, eval = FALSE} -method(print, cat) <- function(...) { - print("I am a cat.") -} - -print(fluffy) -#> "I am a cat" - -``` - -*For validators, inheritance, dynamic properties and more, see the [vignette!](https://rconsortium.github.io/OOP-WG/articles/S7.html)* - -https://rconsortium.github.io/OOP-WG/articles/S7.html - -## So... switch to S7 ? {-} - -$$ -\huge -\textbf{Soon}^{tm} -$$ - -* Not yet... still in development! - -* But consider trying it out: - - * To stay ahead of the curve... S7 will be integrated into base R someday! - - * To contribute feedback to the S7 team! - - * To get "almost all" of the benefits of S4 without the complexity ! - -* In particular, if you have a new project that might require the complexity of S4, consider S7 instead! - -## Meeting Videos - -### Cohort 1 - -`r knitr::include_url("https://www.youtube.com/embed/W1uc8HbyZvI")` - -### Cohort 2 - -`r knitr::include_url("https://www.youtube.com/embed/bzo37PHCM1I")` - -### Cohort 3 - -`r knitr::include_url("https://www.youtube.com/embed/_byYFTQHp1Y")` - -### Cohort 4 - -`r knitr::include_url("https://www.youtube.com/embed/vdKDPBcOc6Y")` - -### Cohort 5 - -`r knitr::include_url("https://www.youtube.com/embed/3EvqtVYTFVM")` - -### Cohort 6 - -`r knitr::include_url("https://www.youtube.com/embed/vEButxFIvLw")` - -
- Meeting chat log - -``` -00:11:36 Oluwafemi Oyedele: I have not built anything with them!!! -00:16:31 Arthur Shaw: https://cran.r-project.org/web/packages/sp/index.html -00:19:05 Arthur Shaw: Apparently Hadley asked the same question we're asking several years ago: https://stackoverflow.com/questions/5437238/which-packages-make-good-use-of-s4-objects -00:19:16 Trevin: HA -00:23:54 Trevin: Your audio is breaking up Federica -01:06:58 Federica Gazzelloni: https://mastering-shiny.org/reactive-motivation.html?q=R6#event-driven -01:07:37 Federica Gazzelloni: https://engineering-shiny.org/common-app-caveats.html?q=R6#using-r6-as-data-storage -01:10:52 Oluwafemi Oyedele: Thank you !!! -``` -
- -### Cohort 7 - -`r knitr::include_url("https://www.youtube.com/embed/2vxnzqWp-OU")` +# Trade-offs + +**Learning objectives:** + +- Understand the Trade-offs between S3, R6 and S4 + +- Brief intro to S7 (the object system formerly known as R7) + + +## Introduction {-} + +* We have three OOP systems introduced so far (S3, S4, R6) + +* At the current time (pre - S7?) Hadley recommends use S3 by default: It's simple and widely used throughout base R and CRAN. + +* If you have experience in other languages, *Resist* the temptation to use R6 even though it will feel more familiar! + + +## S4 versus S3 {-} + +**Which functional object system to use, S3 or S4? ** + +- **S3** is a simple and flexible system. + + - Good for small teams who need flexibility and immediate payoffs. + + - Commonly used throughout base R and CRAN + + - Flexibility can cause problems, more complex systems might require formal conventions + + +- **S4** is a more formal, strict system. + + - Good for large projects and large teams + + - Used by Bioconductor project + + - Requires significant up front investment in design, but payoff is a robust system that enforces conventions. + + - S4 documentation is challenging to use. + + + +## R6 versus S3 {-} + +**R6** is built on **encapsulated objects**, rather than generic functions. + + +**Big differences: general trade-offs** + +* A generic is a regular function so it lives in the global namespace. An R6 method belongs to an object so it lives in a local namespace. This influences how we think about naming. + +* R6's reference semantics allow methods to simultaneously return a value and modify an object. This solves a painful problem called "threading state". + +* You invoke an R6 method using `$`, which is an infix operator. If you set up your methods correctly you can use chains of method calls as an alternative to the pipe. + +## Namespacing {-} + +**Where methods are found?** + +- in S3, **Generic functions** are **global** and live in the **global namespace** + + - Advantage: Uniform API: `summary`, `print`, `predict` etc. + + - Disadvantage: Must be careful about creating new methods! Homonyms must be avoided, don't define `plot(bank_heist)` + + +- in R6, **Encapsulated methods** are **local**: objects with a **scope** + + - Advantage: No problems with homonyms: meaning of `bank_heist$plot()` is clear and unambiguous. + + - Disadvantage: Lack of a uniform API, except by convention. + + +## Threading state {-} + + +In S3 the challenge is to return a value and modify the object. + + +```{r} +new_stack <- function(items = list()) { + structure(list(items = items), class = "stack") +} + +push <- function(x, y) { + x$items <- c(x$items, list(y)) + x +} +``` + +No problem with that, but what about when we want to pop a value? We need to return two things. + +```{r} +pop <- function(x) { + n <- length(x$items) + + item <- x$items[[n]] + x$items <- x$items[-n] + + list(item = item, x = x) +} +``` + +The usage is a bit awkward: + +```{r} +s <- new_stack() +s <- push(s, 10) +s <- push(s, 20) + +out <- pop(s) +# Update state: +s <- out$x + +print(out$item) +``` + + +In python and other languages we have structured binding to make this less awkward. R has the {zeallot} package. For more, see this vignette: + +```{r 16-Trade-offs-5, eval=FALSE} +vignette('unpacking-assignment') +``` + +However, this is all easier in R6 due to the reference semantics! + +```{r} +Stack <- R6::R6Class("Stack", list( + items = list(), + push = function(x) { + self$items <- c(self$items, x) + invisible(self) + }, + pop = function() { + item <- self$items[[self$length()]] + self$items <- self$items[-self$length()] + item + }, + length = function() { + length(self$items) + } +)) + +s <- Stack$new() +s$push(10) +s$push(20) +s$pop() +``` + + +## Method chaining {-} + +Useful to compose functions from left-to-right. + +Use of the operators: + +- S3: `|>` or `%>%` + +- R6: `$` + +```{r} +s$push(44)$push(32)$pop() +``` + + +## Umm... what about S7 ? {-} + +```{r standards, echo = FALSE, fig.cap = "https://xkcd.com/927/"} + +knitr::include_graphics("https://imgs.xkcd.com/comics/standards_2x.png") + +``` + +### Primary references: {-} + +* Docs: + +* Talk by Hadley Wickham + +## S7 briefly {-} + +* S7 is a 'better' version of S3 with some of the 'strictness' of S4. + +``` +"A little bit more complex then S3, with almost all of the features, +all of the payoff of S4" - rstudio conf 2022, Hadley Wickham +``` +* S3 + S4 = S7 + +* Compatible with S3: S7 objects are S3 objects! Can even extend an S3 object with S7 + +* Somewhat compatible with S4, see [compatability vignette](https://rconsortium.github.io/S7/articles/compatibility.html) for details. + +* Helpful error messages! + +* Note that it was previously called R7, but it was changed to "S7" to better reflect that it is functional not encapsulated! + +## Abbreviated introduction based on the vignette {-} + +To install (it's now on CRAN): +```{r, eval=FALSE} +install.packages("S7") +``` + + +```{r, eval=FALSE} +library(S7) +dog <- new_class("dog", properties = list( + name = class_character, + age = class_numeric +)) +dog + + +#> +#> @ name : dog +#> @ parent: +#> @ properties: +#> $ name: +#> $ age : or +``` + +Note the `class_character`, these are S7 classes corresponding to the base classes. + +Now to use it to create an object of class _dog_: + +```{r, eval = FALSE} +lola <- dog(name = "Lola", age = 11) +lola + +#> +#> @ name: chr "Lola" +#> @ age : num 11 +``` + +Properties can be set/read with `@`, with automatic validation ('safety rails') based on the type! + +```{r, eval = FALSE} + +lola@age <- 12 +lola@age + +#> 12 + +lola@age <- "twelve" + +#> Error: @age must be or , not + +``` + +Note the helpful error message! + +Like S3 (and S4) S7 has generics, implemented with `new_generic` and `method` for particular methods: + +```{r, eval = FALSE} +speak <- new_generic("speak", "x") + +method(speak, dog) <- function(x) { + "Woof" +} + +speak(lola) + +#> [1] "Woof" +``` + +If we have another class, we can implement the generic for that too: + +```{r, eval = FALSE} +cat <- new_class("cat", properties = list( + name = class_character, + age = class_double +)) +method(speak, cat) <- function(x) { + "Meow" +} + +fluffy <- cat(name = "Fluffy", age = 5) +speak(fluffy) + +#> [1] "Meow" +``` + +Helpful messages: + +```{r, eval = FALSE} +speak + +#> speak(x, ...) with 2 methods: +#> 1: method(speak, cat) +#> 2: method(speak, dog) +``` + + +"most usage of S7 with S3 will just work" + +```{r, eval = FALSE} +method(print, cat) <- function(...) { + print("I am a cat.") +} + +print(fluffy) +#> "I am a cat" + +``` + +*For validators, inheritance, dynamic properties and more, see the [vignette!](https://rconsortium.github.io/S7/articles/S7.html)* + + +## So... switch to S7 ? {-} + +$$ +\huge +\textbf{Soon}^{tm} +$$ + +* Not yet... still in development! ![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg) + +* But consider trying it out: + + * To stay ahead of the curve... S7 will be integrated into base R someday! + + * To contribute feedback to the S7 team! + + * To get "almost all" of the benefits of S4 without the complexity ! + +* In particular, if you have a new project that might require the complexity of S4, consider S7 instead! + +## OOP system comparison {-} + +| Characteristic | S3 | S4 | S7 | R6 | +|-------|------|------|------|------| +| _Package_ | base R | base R | [S7](https://rconsortium.github.io/S7/) | [R6](https://r6.r-lib.org/) | +| _Programming type_ | Functional | Functional | Functional | Encapulated | +| _Complexity_ | Low | High | Medium | High | +| _Payoff_ | Low | High | High | High | +| _Team size_ | Small | Small-large | Large | ? | +| _Namespace_ | Global | Global? | Global? | Local | +| _Modify in place_ | No | No | No | Yes | +| _Method chaining_ | `|>` | `|>`? | `|>`? | `$` | +| _Get/set component_ | `$` | `@` | `@` | `$` | +| _Create class_ | `class()` or `structure()` with `class` argument | `setClass()` | `new_class()` | `R6Class()` | +| _Create validator_ | `function()` | `setValidity()` or `validator` argument in `setClass()` | `validator` argument in `new_class()` | `$validate()` | +| _Create generic_ | `UseMethod()` | `setGeneric()` | `new_generic()` | NA | +| _Create method_ | `function()` assigned to `generic.method` | `setMethod()` | `method()` | `R6Class()` | +| _Create object_ | `class()` or `structure()` with `class` argument or constructor function | `new()` | Use registered method function | `$new()` | +| _Additional components_ | attributes | slots | properties | | +| | | | | | + +## Meeting Videos {-} + +### Cohort 1 {-} + +`r knitr::include_url("https://www.youtube.com/embed/W1uc8HbyZvI")` + +### Cohort 2 {-} + +`r knitr::include_url("https://www.youtube.com/embed/bzo37PHCM1I")` + +### Cohort 3 {-} + +`r knitr::include_url("https://www.youtube.com/embed/_byYFTQHp1Y")` + +### Cohort 4 {-} + +`r knitr::include_url("https://www.youtube.com/embed/vdKDPBcOc6Y")` + +### Cohort 5 {-} + +`r knitr::include_url("https://www.youtube.com/embed/3EvqtVYTFVM")` + +### Cohort 6 {-} + +`r knitr::include_url("https://www.youtube.com/embed/vEButxFIvLw")` + +
+ Meeting chat log + +``` +00:11:36 Oluwafemi Oyedele: I have not built anything with them!!! +00:16:31 Arthur Shaw: https://cran.r-project.org/web/packages/sp/index.html +00:19:05 Arthur Shaw: Apparently Hadley asked the same question we're asking several years ago: https://stackoverflow.com/questions/5437238/which-packages-make-good-use-of-s4-objects +00:19:16 Trevin: HA +00:23:54 Trevin: Your audio is breaking up Federica +01:06:58 Federica Gazzelloni: https://mastering-shiny.org/reactive-motivation.html?q=R6#event-driven +01:07:37 Federica Gazzelloni: https://engineering-shiny.org/common-app-caveats.html?q=R6#using-r6-as-data-storage +01:10:52 Oluwafemi Oyedele: Thank you !!! +``` +
+ +### Cohort 7 {-} + +`r knitr::include_url("https://www.youtube.com/embed/2vxnzqWp-OU")`