+ - 0:00:00
Notes for current slide
Notes for next slide

Advanced R

Chapter 14 - R6

Tyler Grant Smith

 TylerGrantSmith
 TylerGrantS

2020-07-26

1 / 24

R6

Package: R6
Title: Encapsulated Classes with Reference Semantics
Version: 2.4.1
Authors@R: person("Winston", "Chang", role = c("aut", "cre"), email = "winston@stdout.org")
Description: Creates classes with reference semantics, similar to R's built-in reference classes. Compared to reference classes, R6 classes are simpler and lighter-weight, and they are not built on S4 classes so they do not require the methods package. These classes allow public and private members, and they support inheritance, even when the classes are defined in different packages.
Depends: R (>= 3.0)
Suggests: knitr, microbenchmark, pryr, testthat, ggplot2, scales
License: MIT + file LICENSE
URL: https://r6.r-lib.org, https://github.com/r-lib/R6/
LazyData: true
BugReports: https://github.com/r-lib/R6/issues
RoxygenNote: 6.1.1
NeedsCompilation: no
Packaged: 2019-11-12 20:00:15 UTC; winston
Author: Winston Chang [aut, cre]
Maintainer: Winston Chang
Repository: CRAN
Date/Publication: 2019-11-12 22:50:03 UTC
Built: R 4.0.2; ; 2020-07-21 14:28:09 UTC; windows
2 / 24

R6 in the wild

How many?

depends <- attr(pkgsearch::advanced_search(Depends = "R6"), "metadata")$total
imports <- attr(pkgsearch::advanced_search(Imports = "R6"), "metadata")$total
suggests <- attr(pkgsearch::advanced_search(Suggests = "R6"), "metadata")$total
glue("{depends + imports + suggests} packages on CRAN utilize `R6`.")
## 321 packages on CRAN utilize `R6`.

What are they?

pkgsearch::advanced_search(Imports = "R6", size = 300) %>%
arrange(-downloads_last_month) %>%
head(20) %>%
pull("package") %>%
paste0(collapse = ", ") %>%
strwrap(width = 100) %>%
cli::cat_line()
## dplyr, processx, pkgbuild, testthat, callr, vdiffr, desc, httr, scales, promises, selectr, httpuv,
## readr, shiny, progress, dbplyr, roxygen2, crosstalk, rcmdcheck, data.tree
3 / 24

R6 in the wild

"testthat" %>% getNamespace() %>% eapply(is.R6Class) %>% flatten_lgl() %>% which() %>% names()
## [1] "Stack" "TeamcityReporter" "RstudioReporter" "StopReporter"
## [5] "MultiReporter" "SilentReporter" "Reporter" "TapReporter"
## [9] "MinimalReporter" "CheckReporter" "FailReporter" "LocationReporter"
## [13] "ProgressReporter" "JunitReporter" "ListReporter" "SummaryReporter"
## [17] "DebugReporter"
4 / 24

R6 in the wild

"testthat" %>% getNamespace() %>% eapply(is.R6Class) %>% flatten_lgl() %>% which() %>% names()
## [1] "Stack" "TeamcityReporter" "RstudioReporter" "StopReporter"
## [5] "MultiReporter" "SilentReporter" "Reporter" "TapReporter"
## [9] "MinimalReporter" "CheckReporter" "FailReporter" "LocationReporter"
## [13] "ProgressReporter" "JunitReporter" "ListReporter" "SummaryReporter"
## [17] "DebugReporter"
testthat:::Stack
## <Stack> object generator
## Public:
## initialize: function (init = 20L)
## push: function (..., .list = NULL)
## size: function ()
## as_list: function ()
## clone: function (deep = FALSE)
## Private:
## stack: NULL
## count: 0
## init: 20
## Parent env: <environment: namespace:testthat>
## Locked objects: TRUE
## Locked class: FALSE
## Portable: TRUE
4 / 24

Reference Semantics

  • Value semantics leads to copy on modification
  • Reference semantics leads to reference change on modification

Value Semanics

x <- 1
y <- x
x <- 2
y
## [1] 1

Reference Semantics

x <- new.env()
y <- x
x$a <- 2
y$a
## [1] 2
-
6 / 24

Generating a 🍺 class

The simplest R6 class:

R6::R6Class()
## <unnamed> object generator
## Public:
## clone: function (deep = FALSE)
## Parent env: <environment: R_GlobalEnv>
## Locked objects: TRUE
## Locked class: FALSE
## Portable: TRUE
7 / 24

Generating a 🍺 class

The simplest R6 class:

R6::R6Class()
## <unnamed> object generator
## Public:
## clone: function (deep = FALSE)
## Parent env: <environment: R_GlobalEnv>
## Locked objects: TRUE
## Locked class: FALSE
## Portable: TRUE

You should at least provide the first argument, classname:

Beer <- R6::R6Class("Beer")
7 / 24

Generating a 🍺 class

The simplest R6 class:

R6::R6Class()
## <unnamed> object generator
## Public:
## clone: function (deep = FALSE)
## Parent env: <environment: R_GlobalEnv>
## Locked objects: TRUE
## Locked class: FALSE
## Portable: TRUE

You should at least provide the first argument, classname:

Beer <- R6::R6Class("Beer")
class(Beer)
## [1] "R6ClassGenerator"

I (kinda) Lied

7 / 24

Generating a 🍺 class

The simplest R6 class:

R6::R6Class()
## <unnamed> object generator
## Public:
## clone: function (deep = FALSE)
## Parent env: <environment: R_GlobalEnv>
## Locked objects: TRUE
## Locked class: FALSE
## Portable: TRUE

You should at least provide the first argument, classname:

Beer <- R6::R6Class("Beer")
class(Beer)
## [1] "R6ClassGenerator"

I (kinda) Lied

typeof(Beer)
## [1] "environment"

Wait...what?

7 / 24

Instantiating a class

Beer <- R6::R6Class("Beer")

Instantiate your class using the generator's new method

beer <- Beer$new()
beer
## <Beer>
## Public:
## clone: function (deep = FALSE)

We'll talk about cloning later, but you can disable it by passing cloneable = FALSE when defining the class

Beer <- R6::R6Class("Beer", cloneable = FALSE)
Beer$new()
## <Beer>
## Public:
## :
8 / 24

public

The public argument to R6::R6Class accepts a named list of methods and/or objects.

Beer <- R6::R6Class("Beer", public = list(abv = .05))

External Access

beer <- Beer$new()
beer$abv
## [1] 0.05

Internal access

Beer <- R6::R6Class(
"Beer",
public = list(
abv = 0.05,
percent_abv = function()
sprintf("%.1f%%", 100 * self$abv)
))
beer <- Beer$new()
beer$percent_abv()
## [1] "5.0%"
9 / 24

Socratic Break

I've already called created my class generator. Can I modify it?

10 / 24

Socratic Break

I've already called created my class generator. Can I modify it?

Beer$rating <- 5

Well that was easy!

10 / 24

Socratic Break

I've already called created my class generator. Can I modify it?

Beer$rating <- 5

Well that was easy!

Beer$new()$rating
## NULL

D'oh! I should use the generator's set method instead.

10 / 24

Socratic Break

I've already called created my class generator. Can I modify it?

Beer$rating <- 5

Well that was easy!

Beer$new()$rating
## NULL

D'oh! I should use the generator's set method instead.

Beer$set("public", "rating", 5)
Beer$new()$rating
## [1] 5

Woohoo!

10 / 24

Inheritance

Methods and variables defined in one class (the parent) can be accessed in a subclass (the child).

Pass an R6ClassGenerator name for the inherit parameter of your class call.

IPA <- R6::R6Class("IPA", inherit = Beer)
IPA$new()
## <IPA>
## Inherits from: <Beer>
## Public:
## abv: 0.05
## clone: function (deep = FALSE)
## percent_abv: function ()
## rating: 5

Warning The name is evaluated in the parent_env during instantiation. We will see this later,

11 / 24

Introspection

Instantiated R6 classes are assigned an S3 class that reflects its R6 hierarchy.

DoubleIPA <- R6::R6Class("DoubleIPA", inherit = IPA)
dipa <- DoubleIPA$new()
class(dipa)
## [1] "DoubleIPA" "IPA" "Beer" "R6"
12 / 24

Introspection

Instantiated R6 classes are assigned an S3 class that reflects its R6 hierarchy.

DoubleIPA <- R6::R6Class("DoubleIPA", inherit = IPA)
dipa <- DoubleIPA$new()
class(dipa)
## [1] "DoubleIPA" "IPA" "Beer" "R6"

Unless you don't want it to do that.

NonAlcoholic <- R6::R6Class(
"NonAlcoholic",
inherit = Beer,
class = FALSE)
zima <- NonAlcoholic$new()
class(zima)
## [1] "environment"

12 / 24

private

Variables and methods that are part of the internal behavior of the class

Useful to separate the public interface, similar to non-exported objects in a package.

Beer$set("private", ".name", "Duff")
beer_new <- Beer$new()
beer_new$.name
## NULL
13 / 24

private

Variables and methods that are part of the internal behavior of the class

Useful to separate the public interface, similar to non-exported objects in a package.

Beer$set("private", ".name", "Duff")
beer_new <- Beer$new()
beer_new$.name
## NULL

Internal

Access using private$

Beer$set("public", "get_name",
function() { private$.name })
beer_new <- Beer$new()
beer_new$get_name()
## [1] "Duff"

External

If you just have to peek

beer_new$.__enclos_env__$private$.name
## [1] "Duff"
13 / 24

active

R6 makes use of 's makeActiveBinding function to add active/lazy/delayed bindings.

  • No variable passed Treated like a variable
14 / 24

active

R6 makes use of 's makeActiveBinding function to add active/lazy/delayed bindings.

  • No variable passed Treated like a variable

  • One variable passed Treated like an assignment

14 / 24

active

R6 makes use of 's makeActiveBinding function to add active/lazy/delayed bindings.

  • No variable passed Treated like a variable

  • One variable passed Treated like an assignment

  • Two variables passed Hey now, let's not get crazy.

14 / 24

active

R6 makes use of 's makeActiveBinding function to add active/lazy/delayed bindings.

  • No variable passed Treated like a variable

  • One variable passed Treated like an assignment

  • Two variables passed Hey now, let's not get crazy.

No Variable

Beer$set("active", "name",
function() { private$.name })
new_beer <- Beer$new()
new_beer$name
## [1] "Duff"
new_beer$name <- "Duff Lite"
## Error in (function () : unused argument (base::quote("Duff Lite"))

One Variable

Beer$set("active", "name",
function(n) {
if (missing(n)) { return(private$.name) }
stopifnot(is.character(n) && length(n) == 1)
private$.name <- n
},
overwrite = TRUE
)
new_beer <- Beer$new()
new_beer$name <- "Duff Life"
new_beer$name
## [1] "Duff Life"
14 / 24

initialize

The initialize method is called at the end of the generator's new function.

Beer$set("public", "initialize",
function(name, rating = 5) {
self$name <- name
self$rating <- rating
cli::cat_line(glue("It's five o'clock somewhere. Give me a {self$name}!"))
})
beer <- Beer$new("Breakfast Stout")
## It's five o'clock somewhere. Give me a Breakfast Stout!
15 / 24

initialize

The initialize method is called at the end of the generator's new function.

Beer$set("public", "initialize",
function(name, rating = 5) {
self$name <- name
self$rating <- rating
cli::cat_line(glue("It's five o'clock somewhere. Give me a {self$name}!"))
})
beer <- Beer$new("Breakfast Stout")
## It's five o'clock somewhere. Give me a Breakfast Stout!

You can call the inherited initialize function using super$.

IPA$set("public", "initialize",
function(name, ...) {
name <- snakecase::to_any_case(name, 'random')
super$initialize(name, ...)
})
beer <- IPA$new("King Sue")
## It's five o'clock somewhere. Give me a KInG sUe!
15 / 24

initialize

If any public or private fields have reference semantics (other R6 classes/environments/data.tables) then they should be created in the initialize method to avoid sharing.

Sometimes, this can be useful.

`%||%` <- rlang::`%||%`
SelfCounter <- R6::R6Class(
"SelfCounter",
public = list(
count_env = new.env(),
initialize = function()
self$count_env$counter <-
(self$count_env$counter %||% 0) + 1
)
)
SelfCounter$new()$count_env$counter
## [1] 1
SelfCounter$new()$count_env$counter
## [1] 2
SelfCounter$new()$count_env$counter
## [1] 3
16 / 24

initialize

LessUseful <- R6::R6Class(
"LessUseful",
public = list(
count_env = NULL,
initialize = function() {
self$count_env <- new.env()
self$count_env$counter <-
(self$count_env$counter %||% 0) + 1
})
)
LessUseful$new()$count_env$counter
## [1] 1
LessUseful$new()$count_env$counter
## [1] 1
LessUseful$new()$count_env$counter
## [1] 1
17 / 24

finalize

The finalize method allows a class to clean up after itself. e.g.,

  • Close database connections

  • Close file connections

  • Provide status updates

Beer$set("public", "finalize", function() { cat("Goodnight brew")})
beer <- IPA$new("Sculpin")
## It's five o'clock somewhere. Give me a sCulPIN!
# we hardly knew ye
rm(beer)
# force a garbage collection to get the finalizer to trigger
invisible(gc())
## Goodnight brew
18 / 24

print

R6:::print.R6
## function (x, ...)
## {
## if (is.function(.subset2(x, "print"))) {
## .subset2(x, "print")(...)
## }
## else {
## cat(format(x, ...), sep = "\n")
## }
## invisible(x)
## }
## <bytecode: 0x000000001d6655f8>
## <environment: namespace:R6>

Define your own print in the public members of your class.

Beer$set(
"public", "print",
function(...) {
cat(
glue("{self$name}:{strrep('*',self$rating)}")
)
})
beer <- Beer$new("Duff", 3)
## It's five o'clock somewhere. Give me a Duff!
beer
## Duff:***

A format generic is also defined for R6 objects.

19 / 24

Cloning

beer1 <- Beer$new("Duff")
## It's five o'clock somewhere. Give me a Duff!
beer2 <- beer1
beer2$name <- "Duff Lite"
beer2
## Duff Lite:*****
beer1
## Duff Lite:*****
beer1 <- Beer$new("Duff")
## It's five o'clock somewhere. Give me a Duff!
beer2 <- beer1$clone()
beer2$name <- "Duff Lite"
beer2
## Duff Lite:*****
beer1
## Duff:*****

Additional option deep = TRUE used when you want to make copies of all encapsulated objects that use reference semantics.

20 / 24

Bottles

Beer$set("private", ".bottles", 9L)
Beer$set("active", "bottles", function(b) {
if (missing(b)) { return(private$.bottles) }
private$.bottles <- b
})
Beer$set("public", "drink", function() {
if (self$bottles == 0)
msg <- glue("No more bottles of {self$name} on the wall...\n")
else {
msg <- glue("{self$bottles} bottle{ifelse(self$bottles>1,'s','')} of {self$name}")
msg <- glue("{msg} on the wall. {msg}! Take one down, pass it around.\n")
self$bottles <- self$bottles - 1
}
cli::cat_line(msg)
invisible(self)
})
beer <- Beer$new("Duff")
## It's five o'clock somewhere. Give me a Duff!
21 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
beer$drink()
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
beer$drink()
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
beer$drink()
## 5 bottles of Duff on the wall. 5 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
beer$drink()
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
beer$drink()
## 5 bottles of Duff on the wall. 5 bottles of Duff! Take one down, pass it around.
beer$drink()
## 4 bottles of Duff on the wall. 4 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
beer$drink()
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
beer$drink()
## 5 bottles of Duff on the wall. 5 bottles of Duff! Take one down, pass it around.
beer$drink()
## 4 bottles of Duff on the wall. 4 bottles of Duff! Take one down, pass it around.
beer$drink()
## 3 bottles of Duff on the wall. 3 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
beer$drink()
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
beer$drink()
## 5 bottles of Duff on the wall. 5 bottles of Duff! Take one down, pass it around.
beer$drink()
## 4 bottles of Duff on the wall. 4 bottles of Duff! Take one down, pass it around.
beer$drink()
## 3 bottles of Duff on the wall. 3 bottles of Duff! Take one down, pass it around.
beer$drink()
## 2 bottles of Duff on the wall. 2 bottles of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
beer$drink()
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
beer$drink()
## 5 bottles of Duff on the wall. 5 bottles of Duff! Take one down, pass it around.
beer$drink()
## 4 bottles of Duff on the wall. 4 bottles of Duff! Take one down, pass it around.
beer$drink()
## 3 bottles of Duff on the wall. 3 bottles of Duff! Take one down, pass it around.
beer$drink()
## 2 bottles of Duff on the wall. 2 bottles of Duff! Take one down, pass it around.
beer$drink()
## 1 bottle of Duff on the wall. 1 bottle of Duff! Take one down, pass it around.
22 / 24
beer$drink()
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
beer$drink()
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
beer$drink()
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
beer$drink()
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
beer$drink()
## 5 bottles of Duff on the wall. 5 bottles of Duff! Take one down, pass it around.
beer$drink()
## 4 bottles of Duff on the wall. 4 bottles of Duff! Take one down, pass it around.
beer$drink()
## 3 bottles of Duff on the wall. 3 bottles of Duff! Take one down, pass it around.
beer$drink()
## 2 bottles of Duff on the wall. 2 bottles of Duff! Take one down, pass it around.
beer$drink()
## 1 bottle of Duff on the wall. 1 bottle of Duff! Take one down, pass it around.
beer$drink()
## No more bottles of Duff on the wall...
22 / 24

Method chaining

If a method returns self then you can chain calls together.

Beer$
new("Duff")$
drink()$
drink()$
drink()$
drink()$
drink()$
drink()$
drink()$
drink()$
drink()$
drink()
## It's five o'clock somewhere. Give me a Duff!
## 9 bottles of Duff on the wall. 9 bottles of Duff! Take one down, pass it around.
## 8 bottles of Duff on the wall. 8 bottles of Duff! Take one down, pass it around.
## 7 bottles of Duff on the wall. 7 bottles of Duff! Take one down, pass it around.
## 6 bottles of Duff on the wall. 6 bottles of Duff! Take one down, pass it around.
## 5 bottles of Duff on the wall. 5 bottles of Duff! Take one down, pass it around.
## 4 bottles of Duff on the wall. 4 bottles of Duff! Take one down, pass it around.
## 3 bottles of Duff on the wall. 3 bottles of Duff! Take one down, pass it around.
## 2 bottles of Duff on the wall. 2 bottles of Duff! Take one down, pass it around.
## 1 bottle of Duff on the wall. 1 bottle of Duff! Take one down, pass it around.
## No more bottles of Duff on the wall...
23 / 24

Capsule

I had all intentions of demonstrating how R6 works under-the-hood using fancy diagrams like Hadley creates.
Instead, I will monkey around in R.

But, if you are interested and want to hurt your bra: https://rpubs.com/sumprain/r6

24 / 24

R6

Package: R6
Title: Encapsulated Classes with Reference Semantics
Version: 2.4.1
Authors@R: person("Winston", "Chang", role = c("aut", "cre"), email = "winston@stdout.org")
Description: Creates classes with reference semantics, similar to R's built-in reference classes. Compared to reference classes, R6 classes are simpler and lighter-weight, and they are not built on S4 classes so they do not require the methods package. These classes allow public and private members, and they support inheritance, even when the classes are defined in different packages.
Depends: R (>= 3.0)
Suggests: knitr, microbenchmark, pryr, testthat, ggplot2, scales
License: MIT + file LICENSE
URL: https://r6.r-lib.org, https://github.com/r-lib/R6/
LazyData: true
BugReports: https://github.com/r-lib/R6/issues
RoxygenNote: 6.1.1
NeedsCompilation: no
Packaged: 2019-11-12 20:00:15 UTC; winston
Author: Winston Chang [aut, cre]
Maintainer: Winston Chang
Repository: CRAN
Date/Publication: 2019-11-12 22:50:03 UTC
Built: R 4.0.2; ; 2020-07-21 14:28:09 UTC; windows
2 / 24
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow