forked from hadley/adv-r
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathR6.Rmd
116 lines (85 loc) · 5.11 KB
/
R6.Rmd
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# RC
Reference classes (or RC for short) are the newest OO system in R. They were introduced in version 2.12. They are fundamentally different to S3 and S4 because: \index{RC} \index{reference classes|see{RC}} \index{objects!RC|see{RC}}
* RC methods belong to objects, not functions
* RC objects are mutable: the usual R copy-on-modify semantics do not apply
These properties make RC objects behave more like objects do in most other programming languages, e.g., Python, Ruby, Java, and C#. Reference classes are implemented using R code: they are a special S4 class that wraps around an environment.
### Defining classes and creating objects
Since there aren't any reference classes provided by the base R packages, we'll start by creating one. RC classes are best used for describing stateful objects, objects that change over time, so we'll create a simple class to model a bank account. \index{RC!classes} \index{classes!RC}
Creating a new RC class is similar to creating a new S4 class, but you use `setRefClass()` instead of `setClass()`. The first, and only required argument, is an alphanumeric __name__. While you can use `new()` to create new RC objects, it's good style to use the object returned by `setRefClass()` to generate new objects. (You can also do that with S4 classes, but it's less common.) \indexc{setRefClass()}
```{r}
Account <- setRefClass("Account")
Account$new()
```
`setRefClass()` also accepts a list of name-class pairs that define class __fields__ (equivalent to S4 slots). Additional named arguments passed to `new()` will set initial values of the fields. You can get and set field values with `$`: \index{fields}
```{r}
Account <- setRefClass("Account",
fields = list(balance = "numeric"))
a <- Account$new(balance = 100)
a$balance
a$balance <- 200
a$balance
```
Instead of supplying a class name for the field, you can provide a single argument function which will act as an accessor method. This allows you to add custom behaviour when getting or setting a field. See `?setRefClass` for more details.
Note that RC objects are __mutable__, i.e., they have reference semantics, and are not copied-on-modify: \index{copy-on-modify!exceptions}
```{r}
b <- a
b$balance
a$balance <- 0
b$balance
```
For this reason, RC objects come with a `copy()` method that allow you to make a copy of the object:
```{r}
c <- a$copy()
c$balance
a$balance <- 100
c$balance
```
An object is not very useful without some behaviour defined by __methods__. RC methods are associated with a class and can modify its fields in place. In the following example, note that you access the value of fields with their name, and modify them with `<<-`. You'll learn more about `<<-` in [Environments](#binding). \index{RC!methods} \index{methods!RC} \indexc{<<-}
```{r}
Account <- setRefClass("Account",
fields = list(balance = "numeric"),
methods = list(
withdraw = function(x) {
balance <<- balance - x
},
deposit = function(x) {
balance <<- balance + x
}
)
)
```
You call an RC method in the same way as you access a field:
```{r}
a <- Account$new(balance = 100)
a$deposit(100)
a$balance
```
The final important argument to `setRefClass()` is `contains`. This is the name of the parent RC class to inherit behaviour from. The following example creates a new type of bank account that returns an error preventing the balance from going below 0.
```{r, error = TRUE}
NoOverdraft <- setRefClass("NoOverdraft",
contains = "Account",
methods = list(
withdraw = function(x) {
if (balance < x) stop("Not enough money")
balance <<- balance - x
}
)
)
accountJohn <- NoOverdraft$new(balance = 100)
accountJohn$deposit(50)
accountJohn$balance
accountJohn$withdraw(200)
```
All reference classes eventually inherit from `envRefClass`. It provides useful methods like `copy()` (shown above), `callSuper()` (to call the parent field), `field()` (to get the value of a field given its name), `export()` (equivalent to `as()`), and `show()` (overridden to control printing). See the inheritance section in `setRefClass()` for more details.
### Recognising objects and methods
You can recognise RC objects because they are S4 objects (`isS4(x)`) that inherit from "refClass" (`is(x, "refClass")`). `pryr::otype()` will return "RC". RC methods are also S4 objects, with class `refMethodDef`.
### Method dispatch
Method dispatch is very simple in RC because methods are associated with classes, not functions. When you call `x$f()`, R will look for a method f in the class of x, then in its parent, then its parent's parent, and so on. From within a method, you can call the parent method directly with `callSuper(...)`. \index{RC!method dispatch rules}
### Exercises
1. Use a field function to prevent the account balance from being directly
manipulated. (Hint: create a "hidden" `.balance` field, and read the
help for the fields argument in `setRefClass()`.)
1. I claimed that there aren't any RC classes in base R, but that was a
bit of a simplification. Use `getClasses()` and find which classes
`extend()` from `envRefClass`. What are the classes used for? (Hint:
recall how to look up the documentation for a class.)