-
Notifications
You must be signed in to change notification settings - Fork 59
Expand file tree
/
Copy pathr6_class.R
More file actions
550 lines (531 loc) · 17.7 KB
/
r6_class.R
File metadata and controls
550 lines (531 loc) · 17.7 KB
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
#' Create an R6 reference object generator
#'
#' R6 objects are essentially environments, structured in a way that makes them
#' look like an object in a more typical object-oriented language than R. They
#' support public and private members, as well as inheritance across different
#' packages.
#'
#' An R6 object consists of a public environment, and may also contain a private
#' environment, as well as environments for superclasses. In one sense, the
#' object and the public environment are the same; a reference to the object is
#' identical to a reference to the public environment. But in another sense, the
#' object also consists of the fields, methods, private environment and so on.
#'
#' The \code{active} argument is a list of active binding functions. These
#' functions take one argument. They look like regular variables, but when
#' accessed, a function is called with an optional argument. For example, if
#' \code{obj$x2} is an active binding, then when accessed as \code{obj$x2}, it
#' calls the \code{x2()} function that was in the \code{active} list, with no
#' arguments. However, if a value is assigned to it, as in \code{obj$x2 <- 50},
#' then the function is called with the right-side value as its argument, as in
#' \code{x2(50)}. See \code{\link{makeActiveBinding}} for more information.
#'
#' If the public or private lists contain any items that have reference
#' semantics (for example, an environment), those items will be shared across
#' all instances of the class. To avoid this, add an entry for that item with a
#' \code{NULL} initial value, and then in the \code{initialize} method,
#' instantiate the object and assign it.
#'
#' @section The \code{print} method:
#'
#' R6 object generators and R6 objects have a default \code{print} method to
#' show them on the screen: they simply list the members and parameters (e.g.
#' lock_objects, portable, etc., see above) of the object.
#'
#' The default \code{print} method of R6 objects can be redefined, by
#' supplying a public \code{print} method. (\code{print} members that are not
#' functions are ignored.) This method is automatically called whenever the
#' object is printed, e.g. when the object's name is typed at the command
#' prompt, or when \code{print(obj)} is called. It can also be called directly
#' via \code{obj$print()}. All extra arguments from a \code{print(obj, ...)}
#' call are passed on to the \code{obj$print(...)} method.
#'
#' @section Portable and non-portable classes:
#'
#' When R6 classes are portable (the default), they can be inherited across
#' packages without complication. However, when in portable mode, members must
#' be accessed with \code{self} and \code{private}, as in \code{self$x} and
#' \code{private$y}.
#'
#' When used in non-portable mode, R6 classes behave more like reference
#' classes: inheritance across packages will not work well, and \code{self}
#' and \code{private} are not necessary for accessing fields.
#'
#' @section Cloning objects:
#'
#' R6 objects have a method named \code{clone} by default. To disable this,
#' use \code{cloneable=FALSE}. Having the \code{clone} method present will
#' slightly increase the memory footprint of R6 objects, but since the method
#' will be shared across all R6 objects, the memory use will be negligible.
#'
#' By default, calling \code{x$clone()} on an R6 object will result in a
#' shallow clone. That is, if any fields have reference semantics
#' (environments, R6, or reference class objects), they will not be copied;
#' instead, the clone object will have a field that simply refers to the same
#' object.
#'
#' To make a deep copy, you can use \code{x$clone(deep=TRUE)}. With this
#' option, any fields that are R6 objects will also be cloned; however,
#' environments and reference class objects will not be.
#'
#' If you want different deep copying behavior, you can supply your own
#' private method called \code{deep_clone}. This method will be called for
#' each field in the object, with two arguments: \code{name}, which is the
#' name of the field, and \code{value}, which is the value. Whatever the
#' method returns will be used as the value for the field in the new clone
#' object. You can write a \code{deep_clone} method that makes copies of
#' specific fields, whether they are environments, R6 objects, or reference
#' class objects.
#'
#' @section S3 details:
#'
#' Normally the public environment will have two classes: the one supplied in
#' the \code{classname} argument, and \code{"R6"}. It is possible to get the
#' public environment with no classes, by using \code{class=FALSE}. This will
#' result in faster access speeds by avoiding class-based dispatch of
#' \code{$}. The benefit is negligible in most cases.
#'
#' If a class is a subclass of another, the object will have as its classes
#' the \code{classname}, the superclass's \code{classname}, and \code{"R6"}
#'
#' The primary difference in behavior when \code{class=FALSE} is that, without
#' a class attribute, it won't be possible to use S3 methods with the objects.
#' So, for example, pretty printing (with \code{print.R6Class}) won't be used.
#'
#' @aliases R6
#' @export
#' @param classname Name of the class. The class name is useful primarily for S3
#' method dispatch.
#' @param public A list of public members, which can be functions (methods) and
#' non-functions (fields).
#' @param private An optional list of private members, which can be functions
#' and non-functions.
#' @param active An optional list of active binding functions.
#' @param inherit A R6ClassGenerator object to inherit from; in other words, a
#' superclass. This is captured as an unevaluated expression which is
#' evaluated in \code{parent_env} each time an object is instantiated.
#' @param portable If \code{TRUE} (the default), this class will work with
#' inheritance across different packages. Note that when this is enabled,
#' fields and members must be accessed with \code{self$x} or
#' \code{private$x}; they can't be accessed with just \code{x}.
#' @param parent_env An environment to use as the parent of newly-created
#' objects.
#' @param class Should a class attribute be added to the object? Default is
#' \code{TRUE}. If \code{FALSE}, the objects will simply look like
#' environments, which is what they are.
#' @param lock_objects Should the environments of the generated objects be
#' locked? If locked, new members can't be added to the objects.
#' @param lock_class If \code{TRUE}, it won't be possible to add more members to
#' the generator object with \code{$set}. If \code{FALSE} (the default), then
#' it will be possible to add more members with \code{$set}. The methods
#' \code{$is_locked}, \code{$lock}, and \code{$unlock} can be used to query
#' and change the locked state of the class.
#' @param cloneable If \code{TRUE} (the default), the generated objects will
#' have method named \code{$clone}, which makes a copy of the object.
#' @examples
#' # A queue ---------------------------------------------------------
#' Queue <- R6Class("Queue",
#' public = list(
#' initialize = function(...) {
#' for (item in list(...)) {
#' self$add(item)
#' }
#' },
#' add = function(x) {
#' private$queue <- c(private$queue, list(x))
#' invisible(self)
#' },
#' remove = function() {
#' if (private$length() == 0) return(NULL)
#' # Can use private$queue for explicit access
#' head <- private$queue[[1]]
#' private$queue <- private$queue[-1]
#' head
#' }
#' ),
#' private = list(
#' queue = list(),
#' length = function() base::length(private$queue)
#' )
#' )
#'
#' q <- Queue$new(5, 6, "foo")
#'
#' # Add and remove items
#' q$add("something")
#' q$add("another thing")
#' q$add(17)
#' q$remove()
#' #> [1] 5
#' q$remove()
#' #> [1] 6
#'
#' # Private members can't be accessed directly
#' q$queue
#' #> NULL
#' # q$length()
#' #> Error: attempt to apply non-function
#'
#' # add() returns self, so it can be chained
#' q$add(10)$add(11)$add(12)
#'
#' # remove() returns the value removed, so it's not chainable
#' q$remove()
#' #> [1] "foo"
#' q$remove()
#' #> [1] "something"
#' q$remove()
#' #> [1] "another thing"
#' q$remove()
#' #> [1] 17
#'
#'
#' # Active bindings -------------------------------------------------
#' Numbers <- R6Class("Numbers",
#' public = list(
#' x = 100
#' ),
#' active = list(
#' x2 = function(value) {
#' if (missing(value)) return(self$x * 2)
#' else self$x <- value/2
#' },
#' rand = function() rnorm(1)
#' )
#' )
#'
#' n <- Numbers$new()
#' n$x
#' #> [1] 100
#' n$x2
#' #> [1] 200
#' n$x2 <- 1000
#' n$x
#' #> [1] 500
#'
#' # If the function takes no arguments, it's not possible to use it with <-:
#' n$rand
#' #> [1] 0.2648
#' n$rand
#' #> [1] 2.171
#' # n$rand <- 3
#' #> Error: unused argument (quote(3))
#'
#'
#' # Inheritance -----------------------------------------------------
#' # Note that this isn't very efficient - it's just for illustrating inheritance.
#' HistoryQueue <- R6Class("HistoryQueue",
#' inherit = Queue,
#' public = list(
#' show = function() {
#' cat("Next item is at index", private$head_idx + 1, "\n")
#' for (i in seq_along(private$queue)) {
#' cat(i, ": ", private$queue[[i]], "\n", sep = "")
#' }
#' },
#' remove = function() {
#' if (private$length() - private$head_idx == 0) return(NULL)
#' private$head_idx <<- private$head_idx + 1
#' private$queue[[private$head_idx]]
#' }
#' ),
#' private = list(
#' head_idx = 0
#' )
#' )
#'
#' hq <- HistoryQueue$new(5, 6, "foo")
#' hq$show()
#' #> Next item is at index 1
#' #> 1: 5
#' #> 2: 6
#' #> 3: foo
#' hq$remove()
#' #> [1] 5
#' hq$show()
#' #> Next item is at index 2
#' #> 1: 5
#' #> 2: 6
#' #> 3: foo
#' hq$remove()
#' #> [1] 6
#'
#'
#'
#' # Calling superclass methods with super$ --------------------------
#' CountingQueue <- R6Class("CountingQueue",
#' inherit = Queue,
#' public = list(
#' add = function(x) {
#' private$total <<- private$total + 1
#' super$add(x)
#' },
#' get_total = function() private$total
#' ),
#' private = list(
#' total = 0
#' )
#' )
#'
#' cq <- CountingQueue$new("x", "y")
#' cq$get_total()
#' #> [1] 2
#' cq$add("z")
#' cq$remove()
#' #> [1] "x"
#' cq$remove()
#' #> [1] "y"
#' cq$get_total()
#' #> [1] 3
#'
#'
#' # Non-portable classes --------------------------------------------
#' # By default, R6 classes are portable, which means they can be inherited
#' # across different packages. Portable classes require using self$ and
#' # private$ to access members.
#' # When used in non-portable mode, members can be accessed without self$,
#' # and assignments can be made with <<-.
#'
#' NP <- R6Class("NP",
#' portable = FALSE,
#' public = list(
#' x = NA,
#' getx = function() x,
#' setx = function(value) x <<- value
#' )
#' )
#'
#' np <- NP$new()
#' np$setx(10)
#' np$getx()
#' #> [1] 10
#'
#' # Setting new values ----------------------------------------------
#' # It is possible to add new members to the class after it has been created,
#' # by using the $set() method on the generator.
#'
#' Simple <- R6Class("Simple",
#' public = list(
#' x = 1,
#' getx = function() self$x
#' )
#' )
#'
#' Simple$set("public", "getx2", function() self$x*2)
#'
#' # Use overwrite = TRUE to overwrite existing values
#' Simple$set("public", "x", 10, overwrite = TRUE)
#'
#' s <- Simple$new()
#' s$x
#' s$getx2()
#'
#'
#' # Cloning objects -------------------------------------------------
#' a <- Queue$new(5, 6)
#' a$remove()
#' #> [1] 5
#'
#' # Clone a. New object gets a's state.
#' b <- a$clone()
#'
#' # Can add to each queue separately now.
#' a$add(10)
#' b$add(20)
#'
#' a$remove()
#' #> [1] 6
#' a$remove()
#' #> [1] 10
#'
#' b$remove()
#' #> [1] 6
#' b$remove()
#' #> [1] 20
#'
#'
#' # Deep clones -----------------------------------------------------
#'
#'Simple <- R6Class("Simple",
#' public = list(
#' x = NULL,
#' initialize = function(val) self$x <- val
#' )
#')
#'
#' Cloner <- R6Class("Cloner",
#' public = list(
#' s = NULL,
#' y = 1,
#' initialize = function() self$s <- Simple$new(1)
#' )
#' )
#'
#' a <- Cloner$new()
#' b <- a$clone()
#' c <- a$clone(deep = TRUE)
#'
#' # Modify a
#' a$s$x <- 2
#' a$y <- 2
#'
#' # b is a shallow clone. b$s is the same as a$s because they are R6 objects.
#' b$s$x
#' #> [1] 2
#' # But a$y and b$y are different, because y is just a value.
#' b$y
#' #> [1] 1
#'
#' # c is a deep clone, so c$s is not the same as a$s.
#' c$s$x
#' #> [1] 1
#' c$y
#' #> [1] 1
#'
#'
#' # Deep clones with custom deep_clone method -----------------------
#'
#' CustomCloner <- R6Class("CustomCloner",
#' public = list(
#' e = NULL,
#' s1 = NULL,
#' s2 = NULL,
#' s3 = NULL,
#' initialize = function() {
#' self$e <- new.env(parent = emptyenv())
#' self$e$x <- 1
#' self$s1 <- Simple$new(1)
#' self$s2 <- Simple$new(1)
#' self$s3 <- Simple$new(1)
#' }
#' ),
#' private = list(
#' # When x$clone(deep=TRUE) is called, the deep_clone gets invoked once for
#' # each field, with the name and value.
#' deep_clone = function(name, value) {
#' if (name == "e") {
#' # e1 is an environment, so use this quick way of copying
#' list2env(as.list.environment(value, all.names = TRUE),
#' parent = emptyenv())
#'
#' } else if (name %in% c("s1", "s2")) {
#' # s1 and s2 are R6 objects which we can clone
#' value$clone()
#'
#' } else {
#' # For everything else, just return it. This results in a shallow
#' # copy of s3.
#' value
#' }
#' }
#' )
#' )
#'
#' a <- CustomCloner$new()
#' b <- a$clone(deep = TRUE)
#'
#' # Change some values in a's fields
#' a$e$x <- 2
#' a$s1$x <- 3
#' a$s2$x <- 4
#' a$s3$x <- 5
#'
#' # b has copies of e, s1, and s2, but shares the same s3
#' b$e$x
#' #> [1] 1
#' b$s1$x
#' #> [1] 1
#' b$s2$x
#' #> [1] 1
#' b$s3$x
#' #> [1] 5
#'
#'
#' # Debugging -------------------------------------------------------
#' \dontrun{
#' # This will enable debugging the getx() method for objects of the 'Simple'
#' # class that are instantiated in the future.
#' Simple$debug("getx")
#' s <- Simple$new()
#' s$getx()
#'
#' # Disable debugging for future instances:
#' Simple$undebug("getx")
#' s <- Simple$new()
#' s$getx()
#'
#' # To enable and disable debugging for a method in a single instance of an
#' # R6 object (this will not affect other objects):
#' s <- Simple$new()
#' debug(s$getx)
#' s$getx()
#' undebug(s$getx)
#' }
# This function is encapsulated so that it is bound in the R6 namespace, but
# enclosed in the capsule environment
R6Class <- encapsulate(function(classname = NULL, public = list(),
private = NULL, active = NULL,
inherit = NULL, lock_objects = TRUE,
class = TRUE, portable = TRUE,
lock_class = FALSE, cloneable = TRUE,
parent_env = parent.frame()) {
if (!all_named(public) || !all_named(private) || !all_named(active))
stop("All elements of public, private, and active must be named.")
allnames <- c(names(public), names(private), names(active))
if (any(duplicated(allnames)))
stop("All items in public, private, and active must have unique names.")
if ("clone" %in% allnames)
stop("Cannot add a member with reserved name 'clone'.")
if (any(c("self", "private", "super") %in%
c(names(public), names(private), names(active))))
stop("Items cannot use reserved names 'self', 'private', and 'super'.")
if ("initialize" %in% c(names(private), names(active)))
stop("'initialize' is not allowed in private or active.")
if (length(get_nonfunctions(active)) != 0)
stop("All items in active must be functions.")
# Create the generator object, which is an environment
generator <- new.env(parent = capsule)
generator$self <- generator
# Set the generator functions to eval in the generator environment, and copy
# them into the generator env.
generator_funs <- assign_func_envs(generator_funs, generator)
list2env2(generator_funs, generator)
generator$classname <- classname
generator$active <- active
generator$portable <- portable
generator$cloneable <- cloneable
generator$parent_env <- parent_env
generator$lock_objects <- lock_objects
generator$class <- class
generator$lock_class <- lock_class
# Separate fields from methods
generator$public_fields <- get_nonfunctions(public)
generator$private_fields <- get_nonfunctions(private)
generator$public_methods <- get_functions(public)
generator$private_methods <- get_functions(private)
if (cloneable)
generator$public_methods$clone <- generator_funs$clone_method
# Capture the unevaluated expression for the superclass; when evaluated in
# the parent_env, it should return the superclass object.
generator$inherit <- substitute(inherit)
# Names of methods for which to enable debugging
generator$debug_names <- character(0)
attr(generator, "name") <- paste0(classname, "_generator")
class(generator) <- "R6ClassGenerator"
# Print message; in a future version, this will be upgraded to a warning.
if ("finalize" %in% names(generator$public_methods)) {
message(
"R6Class ", classname,
": finalize() method is public, but it should be private as of R6 2.4.0. ",
"This code will continue to work, but in a future version of R6, ",
"finalize() will be required to be private."
)
}
generator
})
#' @exportS3Method utils::.DollarNames
.DollarNames.R6 <- function(x, pattern) {
names <- ls(x, all.names = TRUE)
names <- names[grepl(pattern, names)]
setdiff(names, c(".__enclos_env__", "initialize"))
}