In functional OOP, like S3, methods belong to functions.
In encapsulated OOP, like R6, methods belong to objects.
R6 objects are always modified in place and never copied on modify
Powerful for abstracting complex objects with lots of self-contained components you might want to update
Can produce spooky results and spookier code if you're not careful
All we'll need is
library(R6)
Create R6 classes by calling R6::R6Class()
and passing a list of methods and fields
Using exercise 14.2.6.1 as an example:
BankAccount <- R6Class("BankAccount", list( balance = 0, deposit = function(x) { self$balance <- self$balance + x invisible(self) }, withdraw = function(x) { self$balance <- self$balance - x invisible(self) }))
self$
lets methods reference other fields or methods internal to the object
Create a new instance of a class with the $new()
method
checking <- BankAccount$new()
Access fields and methods with $
checking$balance
## [1] 0
checking$deposit(10)checking$balance
## [1] 10
Methods called for their side-effects (like setting internal values) can be chained together
checking$withdraw(10)$withdraw(10)checking$balance
## [1] -10
This is powered by having side-effect methods return the object invisibly
withdraw = function(x) { self$balance <- self$balance - x invisible(self) }
Some methods affect the behavior of objects in special ways
$initialize()
overrides the default behavior of $new()
BankAccount <- R6Class("BankAccount", list( balance = 0, pwd = NULL, initialize = function(pwd) { self$pwd <- pwd }, deposit = function(x, pwd) { stopifnot(pwd == self$pwd) self$balance <- self$balance + x invisible(self) }, withdraw = function(x, pwd) { stopifnot(pwd == self$pwd) self$balance <- self$balance - x invisible(self) }))
Some methods affect the behavior of objects in special ways
$print()
determines how the object is printed
BankAccount <- R6Class("BankAccount", list(... print = function(...) { cat("Balance:", scales::dollar(self$balance)) invisible(self) }...
savings <- BankAccount$new(pwd = "dont-tell")try(savings$deposit(10, "password123"))
## Error in savings$deposit(10, "password123") : pwd == self$pwd is not TRUE
(savings$deposit(10, "dont-tell"))
## Balance: $10
Beware! Objects encapsulate methods so our old BankAccount
objects don't retroactively get newly created methods
checking
## <BankAccount>## Public:## balance: -10## clone: function (deep = FALSE) ## deposit: function (x) ## withdraw: function (x)
checking <- BankAccount$new(pwd = "dont-tell")checking
## Balance: $0
Make sure you rebuild objects when you alter a class during interactive use
R6 classes can be subclasses of other R6 classes. Define that relationship using the inherit
argument to R6Class()
SocialistBankAccount <- R6Class("SocialistBankAccount", inherit = BankAccount, public = list( check_balance = function() { if (self$balance > 100000) { cat("From each according to their ability!") self$balance <- 100000 } else if (self$balance < 0) { cat("To each according to their need!") self$balance <- 100 } }, deposit = function(x, pwd) { super$deposit(x, pwd) self$check_balance() }, withdraw = function(x, pwd) { ...
Our subclass inherits the methods and fields we don't explicitly overwrite from its super class
common_fund <- SocialistBankAccount$new(pwd = "dont-tell")common_fund
## Balance: $0
Our subclass inherits the methods and fields we don't explicitly overwrite from its super class
common_fund <- SocialistBankAccount$new(pwd = "dont-tell")common_fund
## Balance: $0
super$
allows us to refer to superclass methods and thereby "delegate" like with NextMethod()
in S3
deposit = function(x, pwd) { super$deposit(x, pwd) self$check_balance() }
deposit = function(x, pwd) { super$deposit(x, pwd) self$check_balance() }
common_fund$withdraw(10, "dont-tell")
## To each according to their need!
common_fund
## Balance: $100
deposit = function(x, pwd) { super$deposit(x, pwd) self$check_balance() }
common_fund$withdraw(10, "dont-tell")
## To each according to their need!
common_fund
## Balance: $100
R6 objects also get S3 classes which automatically reproduce the sub/superclass relationships
class(common_fund)
## [1] "SocialistBankAccount" "BankAccount" "R6"
Right now users have full access to internal elements of our objects
checking$pwd
## [1] "dont-tell"
We can use the private
argument of R6Class()
to set components for internal use
SecureBankAccount <- R6Class("SecureBankAccount", public = list( balance = 0, initialize = function(pwd) { private$pwd <- pwd },... More methods ... ), private = list(pwd = NULL))
SecureBankAccount <- R6Class("SecureBankAccount", public = list( balance = 0, initialize = function(pwd) { private$pwd <- pwd },... More methods ... ), private = list(pwd = NULL))
secure_checking <- SecureBankAccount$new("dont-tell")secure_checking$pwd
## NULL
Just reference private$
in methods rather than self$
R6 objects are always modified in place. To get a copy you can use the $clone()
method.
R6 objects are always modified in place. To get a copy you can use the $clone()
method.
The fact that methods of an object can change the object itself makes code harder to reason about.
Hadley's example:
What can we say about the effect of this line of code on x
and y
given that they're base objects? Given that they're R6 objects?
z <- f(x, y)
R6 objects are always modified in place. To get a copy you can use the $clone()
method.
The fact that methods of an object can change the object itself makes code harder to reason about.
Hadley's example:
What can we say about the effect of this line of code on x
and y
given that they're base objects? Given that they're R6 objects?
z <- f(x, y)
If f
calls methods of x
and y
it might change them. In our BankAccount
example the only thing our methods did was change internal values.
But therein lies the power:
14.6.2.3 Why can’t you model a bank account or a deck of cards with an S3 class?
But therein lies the power:
14.6.2.3 Why can’t you model a bank account or a deck of cards with an S3 class?
S3 objects are copied when they're changed so the best you could do is have a generic function return a modified version of the object
R6 objects behave unintuitively when the default value of a field is another R6 object
Number <- R6Class("Number", list( value = 0, increment = function() { self$value <- self$value + 1 }) )NumberPointer <- R6Class("NumberPointer", list( number = Number$new() ))
The instance of Number
will be shared across all instances of NumberPointer
x <- NumberPointer$new()y <- NumberPointer$new()
x <- NumberPointer$new()y <- NumberPointer$new()
x$number$value
## [1] 0
x <- NumberPointer$new()y <- NumberPointer$new()
x$number$value
## [1] 0
y$number$value
## [1] 0
x <- NumberPointer$new()y <- NumberPointer$new()
x$number$value
## [1] 0
y$number$value
## [1] 0
x$number$increment()
x <- NumberPointer$new()y <- NumberPointer$new()
x$number$value
## [1] 0
y$number$value
## [1] 0
x$number$increment()
x$number$value
## [1] 1
x <- NumberPointer$new()y <- NumberPointer$new()
x$number$value
## [1] 0
y$number$value
## [1] 0
x$number$increment()
x$number$value
## [1] 1
y$number$value
## [1] 1
x <- NumberPointer$new()y <- NumberPointer$new()
x$number$value
## [1] 0
y$number$value
## [1] 0
x$number$increment()
x$number$value
## [1] 1
y$number$value
## [1] 1
Avoid this by making sure objects are initialized within a method so you get a new instance every time
I haven't!
I haven't!
But this reminded me of how some machine learning and optimization algorithms are implemented in Python
In functional OOP, like S3, methods belong to functions.
In encapsulated OOP, like R6, methods belong to objects.
R6 objects are always modified in place and never copied on modify
Powerful for abstracting complex objects with lots of self-contained components you might want to update
Can produce spooky results and spookier code if you're not careful
All we'll need is
library(R6)
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 |