-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathR6.qmd
555 lines (385 loc) · 24 KB
/
R6.qmd
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
551
552
553
554
555
# R6 {#sec-r6}
```{r, include = FALSE}
source("common.R")
```
## Introducción
\index{R6}
Este capítulo describe el sistema R6 OOP. R6 tiene dos propiedades especiales:
- Utiliza el paradigma OOP encapsulado, lo que significa que los métodos pertenecen a los objetos, no a los genéricos, y los llamas como `object$method()`.
- Los objetos R6 son **mutables**, lo que significa que se modifican en su lugar y, por lo tanto, tienen semántica de referencia.
Si aprendió programación orientada a objetos en otro lenguaje de programación, es probable que R6 se sienta muy natural y se incline a preferirlo a S3. Resista la tentación de seguir el camino de menor resistencia: en la mayoría de los casos, R6 lo llevará a un código R no idiomático. Volveremos a este tema en la @sec-s3-r6.
R6 es muy similar a un sistema OOP base llamado **clases de referencia**, o RC para abreviar. Describo por qué enseño R6 y no RC en la @sec-why-r6.
### Estructura {.unnumbered}
- La @sec-r6-classes introduce `R6::R6Class()`, la única función que necesita saber para crear clases R6. Aprenderá sobre el método constructor, `$new()`, que le permite crear objetos R6, así como otros métodos importantes como `$initialize()` y `$print()`.
- La @sec-r6-access analiza los mecanismos de acceso de R6: campos privados y activos. Juntos, estos le permiten ocultar datos del usuario o exponer datos privados para leer pero no escribir.
- La @sec-r6-semantics explora las consecuencias de la semántica de referencia de R6. Aprenderá sobre el uso de finalizadores para limpiar automáticamente cualquier operación realizada en el inicializador y un problema común si usa un objeto R6 como un campo en otro objeto R6.
- La @sec-why-r6 describe por qué cubro R6, en lugar del sistema RC base.
### Requisitos previos {.unnumbered}
Debido a que [R6](https://r6.r-lib.org) no está integrado en la base R, deberá instalar y cargar el paquete R6 para usarlo:
```{r setup}
# install.packages("R6")
library(R6)
```
Los objetos R6 tienen semántica de referencia, lo que significa que se modifican en el lugar, no se copian al modificar. Si no está familiarizado con estos términos, repase su vocabulario leyendo la @sec-modify-in-place.
## Clases y métodos {#sec-r6-classes}
\index{R6!classes} \index{classes!R6} \index{self} \index{R6!R6Class@\texttt{R6Class()}}
R6 solo necesita una única llamada de función para crear tanto la clase como sus métodos: `R6::R6Class()`. ¡Esta es la única función del paquete que usará![^r6-1]
[^r6-1]: Eso significa que si está creando R6 en un paquete, solo necesita asegurarse de que esté listado en el campo `Imports` de `DESCRIPCIÓN`. No hay necesidad de importar el paquete a `NAMESPACE`.
El siguiente ejemplo muestra los dos argumentos más importantes para `R6Class()`:
- El primer argumento es el `classname`. No es estrictamente necesario, pero mejora los mensajes de error y permite usar objetos R6 con genéricos S3. Por convención, las clases R6 tienen nombres `UpperCamelCase`.
- El segundo argumento, `public`, proporciona una lista de métodos (funciones) y campos (cualquier otra cosa) que conforman la interfaz pública del objeto. Por convención, los métodos y campos usan `snake_case`. Los métodos pueden acceder a los métodos y campos del objeto actual a través de `self$`.[^r6-2] \index{methods!R6}
[^r6-2]: A diferencia de Python, R6 proporciona automáticamente la variable `self` y no forma parte de la firma del método.
```{r}
Accumulator <- R6Class("Accumulator", list(
sum = 0,
add = function(x = 1) {
self$sum <- self$sum + x
invisible(self)
})
)
```
Siempre debe asignar el resultado de `R6Class()` a una variable con el mismo nombre que la clase, porque `R6Class()` devuelve un objeto R6 que define la clase:
```{r}
Accumulator
```
\index{constructors!R6} Construyes un nuevo objeto a partir de la clase llamando al método `new()`. En R6, los métodos pertenecen a los objetos, por lo que usa `$` para acceder a `new()`:
```{r}
x <- Accumulator$new()
```
A continuación, puede llamar a los métodos y acceder a los campos con `$`:
```{r}
x$add(4)
x$sum
```
En esta clase, los campos y métodos son públicos, lo que significa que puede obtener o establecer el valor de cualquier campo. Más adelante, veremos cómo usar campos y métodos privados para evitar el acceso casual a las partes internas de su clase.
Para que quede claro cuando hablamos de campos y métodos en lugar de variables y funciones, pondré el prefijo `$` en sus nombres. Por ejemplo, la clase `Accumulate` tiene el campo `$sum` y el método `$add()`.
### Encadenamiento de métodos
\index{method chaining}
`$add()` se llama principalmente por su efecto secundario de actualizar `$sum`.
```{r}
Accumulator <- R6Class("Accumulator", list(
sum = 0,
add = function(x = 1) {
self$sum <- self$sum + x
invisible(self)
})
)
```
Los métodos R6 de efectos secundarios siempre deben devolver `self` de forma invisible. Esto devuelve el objeto "actual" y hace posible encadenar varias llamadas a métodos:
```{r}
x$add(10)$add(10)$sum
```
Para facilitar la lectura, puede poner una llamada de método en cada línea:
```{r}
x$
add(10)$
add(10)$
sum
```
Esta técnica se llama **encadenamiento de métodos** y se usa comúnmente en lenguajes como Python y JavaScript. El encadenamiento de métodos está profundamente relacionado con la tubería, y discutiremos los pros y los contras de cada enfoque en la @sec-tradeoffs-pipe.
### Métodos importantes {#sec-r6-important-methods}
\index{R6!methods!print} \index{R6!methods!initialize}
Hay dos métodos importantes que deben definirse para la mayoría de las clases: `$initialize()` y `$print()`. No son obligatorios, pero proporcionarlos hará que su clase sea más fácil de usar.
`$initialize()` anula el comportamiento predeterminado de `$new()`. Por ejemplo, el siguiente código define una clase de Persona con los campos `$name` y `$age`. Para asegurar que `$name` sea siempre una sola cadena, y `$age` sea siempre un solo número, puse controles en `$initialize()`.
```{r, error = TRUE}
Person <- R6Class("Person", list(
name = NULL,
age = NA,
initialize = function(name, age = NA) {
stopifnot(is.character(name), length(name) == 1)
stopifnot(is.numeric(age), length(age) == 1)
self$name <- name
self$age <- age
}
))
hadley <- Person$new("Hadley", age = "thirty-eight")
hadley <- Person$new("Hadley", age = 38)
```
Si tiene requisitos de validación más costosos, impleméntelos en un `$validate()` separado y solo llame cuando sea necesario.
Definir `$print()` le permite anular el comportamiento de impresión predeterminado. Como con cualquier método R6 llamado por sus efectos secundarios, `$print()` debería devolver `invisible(self)`.
```{r}
Person <- R6Class("Person", list(
name = NULL,
age = NA,
initialize = function(name, age = NA) {
self$name <- name
self$age <- age
},
print = function(...) {
cat("Person: \n")
cat(" Name: ", self$name, "\n", sep = "")
cat(" Age: ", self$age, "\n", sep = "")
invisible(self)
}
))
hadley2 <- Person$new("Hadley")
hadley2
```
Este código ilustra un aspecto importante de R6. Debido a que los métodos están vinculados a objetos individuales, el objeto `hadley` creado previamente no obtiene este nuevo método:
```{r}
hadley
hadley$print
```
Desde la perspectiva de R6, no hay relación entre `hadley` y `hadley2`; coincidentemente comparten el mismo nombre de clase. Esto no causa problemas cuando se usan objetos R6 ya desarrollados, pero puede hacer que la experimentación interactiva sea confusa. Si está cambiando el código y no puede averiguar por qué los resultados de las llamadas a métodos no son diferentes, asegúrese de haber reconstruido los objetos R6 con la nueva clase.
### Agregar métodos después de la creación
\index{R6!methods!adding extra}
En lugar de crear continuamente nuevas clases, también es posible modificar los campos y métodos de una clase existente. Esto es útil al explorar de forma interactiva o cuando tiene una clase con muchas funciones que le gustaría dividir en partes. Agrega nuevos elementos a una clase existente con `$set()`, proporcionando la visibilidad (más información en la @sec-r6-access), el nombre y el componente.
```{r, eval = FALSE}
Accumulator <- R6Class("Accumulator")
Accumulator$set("public", "sum", 0)
Accumulator$set("public", "add", function(x = 1) {
self$sum <- self$sum + x
invisible(self)
})
```
Como se indicó anteriormente, los nuevos métodos y campos solo están disponibles para nuevos objetos; no se agregan retrospectivamente a los objetos existentes.
### Herencia
\index{R6!inheritance} \index{inheritance!R6}
Para heredar el comportamiento de una clase existente, proporcione el objeto de la clase al argumento `inherit`:
```{r}
AccumulatorChatty <- R6Class("AccumulatorChatty",
inherit = Accumulator,
public = list(
add = function(x = 1) {
cat("Adding ", x, "\n", sep = "")
super$add(x = x)
}
)
)
x2 <- AccumulatorChatty$new()
x2$add(10)$add(1)$sum
```
`$add()` anula la implementación de la superclase, pero aún podemos delegar a la implementación de la superclase usando `super$`. (Esto es análogo a `NextMethod()` en S3, como se explica en la @sec-s3-inheritance.) Cualquier método que no se invalide utilizará la implementación en la clase principal.
### Introspección
\index{R6!introspection}
Cada objeto R6 tiene una clase S3 que refleja su jerarquía de clases R6. Esto significa que la forma más fácil de determinar la clase (y todas las clases de las que hereda) es usar `class()`:
```{r}
class(hadley2)
```
La jerarquía S3 incluye la clase base "R6". Esto proporciona un comportamiento común, incluido un método `print.R6()` que llama a `$print()`, como se describe arriba.
\index{R6!methods!listing} Puede enumerar todos los métodos y campos con `names()`:
```{r}
names(hadley2)
```
Definimos `$name`, `$age`, `$print` e `$initialize`. Como sugiere el nombre, `.__enclos_env__` es un detalle de implementación interna que no debe tocar; volveremos a `$clone()` en la @sec-r6-semantics.
### Ejercicios
1. Cree una cuenta bancaria clase R6 que almacene un saldo y le permita depositar y retirar dinero. Cree una subclase que arroje un error si intenta entrar en sobregiro. Cree otra subclase que le permita entrar en sobregiro, pero le cobre una tarifa.
2. Cree una clase R6 que represente un mazo de cartas barajado. Deberías poder sacar cartas del mazo con `$draw(n)`, devolver todas las cartas al mazo y volver a barajar con `$reshuffle()`. Use el siguiente código para hacer un vector de tarjetas.
```{r}
suit <- c("♠", "♥", "♦", "♣")
value <- c("A", 2:10, "J", "Q", "K")
cards <- paste0(rep(value, 4), suit)
```
3. ¿Por qué no puedes modelar una cuenta bancaria o una baraja de cartas con una clase S3?
4. Cree una clase R6 que le permita obtener y establecer la zona horaria actual. Puede acceder a la zona horaria actual con `Sys.timezone()` y configurarla con `Sys.setenv(TZ = "newtimezone")`. Al configurar la zona horaria, asegúrese de que la nueva zona horaria esté en la lista proporcionada por `OlsonNames()`.
5. Cree una clase R6 que administre el directorio de trabajo actual. Debe tener los métodos `$get()` y `$set()`.
6. ¿Por qué no puede modelar la zona horaria o el directorio de trabajo actual con una clase S3?
7. ¿Sobre qué tipo base se construyen los objetos R6? ¿Qué atributos tienen?
## Control de acceso {#sec-r6-access}
\index{R6!access control}
`R6Class()` tiene otros dos argumentos que funcionan de manera similar a `public`:
- `private` te permite crear campos y métodos que solo están disponibles dentro de la clase, no fuera de ella.
- `activo` le permite usar funciones de acceso para definir campos dinámicos o activos.
Estos se describen en las siguientes secciones.
### Privacidad
\index{R6!methods!private}
Con R6 puedes definir campos y métodos **privados**, elementos a los que solo se puede acceder desde dentro de la clase, no desde fuera[^r6-3]. Hay dos cosas que debe saber para aprovechar los elementos privados:
[^r6-3]: Debido a que R es un lenguaje tan flexible, técnicamente aún es posible acceder a valores privados, pero tendrá que esforzarse mucho más, profundizando en los detalles de la implementación de R6.
- El argumento 'privado' de 'R6Class' funciona de la misma manera que el argumento 'publico': le da una lista con nombre de métodos (funciones) y campos (todo lo demás).
- Los campos y métodos definidos en `private` están disponibles dentro de los métodos que usan `private$` en lugar de `self$`. No puede acceder a campos o métodos privados fuera de la clase.
Para concretar esto, podríamos hacer que los campos `$age` y `$name` de la clase Persona sean privados. Con esta definición de `Person` solo podemos establecer `$age` y `$name` durante la creación del objeto, y no podemos acceder a sus valores desde fuera de la clase.
```{r}
Person <- R6Class("Person",
public = list(
initialize = function(name, age = NA) {
private$name <- name
private$age <- age
},
print = function(...) {
cat("Person: \n")
cat(" Name: ", private$name, "\n", sep = "")
cat(" Age: ", private$age, "\n", sep = "")
}
),
private = list(
age = NA,
name = NULL
)
)
hadley3 <- Person$new("Hadley")
hadley3
hadley3$name
```
La distinción entre campos públicos y privados es importante cuando crea redes complejas de clases y desea dejar lo más claro posible qué está bien que otros accedan. Cualquier cosa que sea privada puede refactorizarse más fácilmente porque sabe que otros no confían en ella. Los métodos privados tienden a ser menos importantes en R en comparación con otros lenguajes de programación porque las jerarquías de objetos en R tienden a ser más simples.
### Campos activos {#sec-active-fields}
\index{R6!active fields} \index{active bindings}
Los campos activos le permiten definir componentes que parecen campos desde el exterior, pero se definen con funciones, como métodos. Los campos activos se implementan mediante **enlaces activos** (@sec-advanced-bindings). Cada enlace activo es una función que toma un único argumento: `value`. Si el argumento es `missing()`, se está recuperando el valor; de lo contrario, se está modificando.
Por ejemplo, podría crear un campo activo `random` que devuelva un valor diferente cada vez que acceda a él:
```{r}
Rando <- R6::R6Class("Rando", active = list(
random = function(value) {
if (missing(value)) {
runif(1)
} else {
stop("Can't set `$random`", call. = FALSE)
}
}
))
x <- Rando$new()
x$random
x$random
x$random
```
\index{validators!R6} Los campos activos son especialmente útiles junto con los campos privados, ya que permiten implementar componentes que parecen campos desde el exterior pero proporcionan comprobaciones adicionales. Por ejemplo, podemos usarlos para crear un campo `age` de solo lectura y para asegurarnos de que `name` sea un vector de caracteres de longitud 1.
```{r, error = TRUE}
Person <- R6Class("Person",
private = list(
.age = NA,
.name = NULL
),
active = list(
age = function(value) {
if (missing(value)) {
private$.age
} else {
stop("`$age` is read only", call. = FALSE)
}
},
name = function(value) {
if (missing(value)) {
private$.name
} else {
stopifnot(is.character(value), length(value) == 1)
private$.name <- value
self
}
}
),
public = list(
initialize = function(name, age = NA) {
private$.name <- name
private$.age <- age
}
)
)
hadley4 <- Person$new("Hadley", age = 38)
hadley4$name
hadley4$name <- 10
hadley4$age <- 20
```
### Ejercicios
1. Cree una clase de cuenta bancaria que le impida establecer directamente el saldo de la cuenta, pero aún puede retirar y depositar. Lanza un error si intentas entrar en sobregiro.
2. Cree una clase con un campo `$password` de solo escritura. Debería tener el método `$check_password(password)` que devuelva `TRUE` o `FALSE`, pero no debería haber forma de ver la contraseña completa.
3. Extienda la clase `Rando` con otro enlace activo que le permita acceder al valor aleatorio anterior. Asegúrese de que el enlace activo sea la única forma de acceder al valor.
4. ¿Pueden las subclases acceder a campos/métodos privados desde su padre? Haz un experimento para averiguarlo.
## Semántica de referencia {#sec-r6-semantics}
\index{reference semantics}
Una de las grandes diferencias entre R6 y la mayoría de los demás objetos es que tienen semántica de referencia. La consecuencia principal de la semántica de referencia es que los objetos no se copian cuando se modifican:
```{r}
y1 <- Accumulator$new()
y2 <- y1
y1$add(10)
c(y1 = y1$sum, y2 = y2$sum)
```
En cambio, si desea una copia, deberá explícitamente `$clone()` el objeto:
```{r}
y1 <- Accumulator$new()
y2 <- y1$clone()
y1$add(10)
c(y1 = y1$sum, y2 = y2$sum)
```
(`$clone()` no clona recursivamente objetos R6 anidados. Si quieres eso, tendrás que usar `$clone(deep = TRUE)`.)
Hay otras tres consecuencias menos obvias:
- Es más difícil razonar sobre el código que usa objetos R6 porque necesita comprender más contexto.
- Tiene sentido pensar en cuándo se elimina un objeto R6 y puede escribir `$finalize()` para complementar el `$initialize()`.
- Si uno de los campos es un objeto R6, debe crearlo dentro `$initialize()`, no `R6Class()`.
Estas consecuencias se describen con más detalle a continuación.
### Razonamiento
En general, la semántica de referencia hace que sea más difícil razonar sobre el código. Tome este ejemplo muy simple:
```{r, eval = FALSE}
x <- list(a = 1)
y <- list(b = 2)
z <- f(x, y)
```
Para la gran mayoría de las funciones, sabes que la línea final solo modifica `z`.
Tome un ejemplo similar que usa una clase de referencia `List` imaginaria:
```{r, eval = FALSE}
x <- List$new(a = 1)
y <- List$new(b = 2)
z <- f(x, y)
```
La línea final es mucho más difícil de razonar: si `f()` llama a métodos de `x` o `y`, podría modificarlos así como `z`. Este es el mayor inconveniente potencial de R6 y debe tener cuidado de evitarlo escribiendo funciones que devuelvan un valor o modifiquen sus entradas R6, pero no ambos. Dicho esto, hacer ambas cosas puede conducir a un código sustancialmente más simple en algunos casos, y discutiremos esto más adelante en la @sec-threading-state).
### Finalizador
\index{R6!methods!finalizer} \index{finalizers}
Una propiedad útil de la semántica de referencia es que tiene sentido pensar cuándo se **finaliza** un objeto R6, es decir, cuándo se elimina. Esto no tiene sentido para la mayoría de los objetos porque la semántica de copiar al modificar significa que puede haber muchas versiones transitorias de un objeto, como se menciona en la @sec-gc. Por ejemplo, lo siguiente crea dos objetos de factor: el segundo se crea cuando se modifican los niveles, dejando que el primero sea destruido por el recolector de basura.
```{r}
x <- factor(c("a", "b", "c"))
levels(x) <- c("c", "b", "a")
```
Dado que los objetos R6 no se copian al modificarse, solo se eliminan una vez, y tiene sentido pensar en `$finalize()` como un complemento de `$initialize()`. Los finalizadores generalmente juegan un papel similar a `on.exit()` (como se describe en la @sec-on-exit), limpiando cualquier recurso creado por el inicializador. Por ejemplo, la siguiente clase envuelve un archivo temporal y lo elimina automáticamente cuando finaliza la clase.
```{r}
TemporaryFile <- R6Class("TemporaryFile", list(
path = NULL,
initialize = function() {
self$path <- tempfile()
},
finalize = function() {
message("Cleaning up ", self$path)
unlink(self$path)
}
))
```
El método finalize se ejecutará cuando se elimine el objeto (o más precisamente, por la primera recolección de elementos no utilizados después de que el objeto se haya desvinculado de todos los nombres) o cuando R salga. Esto significa que el finalizador se puede llamar de manera efectiva en cualquier parte de su código R y, por lo tanto, es casi imposible razonar sobre el código del finalizador que toca las estructuras de datos compartidas. Evite estos posibles problemas utilizando únicamente el finalizador para limpiar los recursos privados asignados por el inicializador.
```{r, result = FALSE}
tf <- TemporaryFile$new()
rm(tf)
#> Cleaning up /tmp/Rtmpk73JdI/file155f31d8424bd
```
### Campos de R6
\index{mutable default arguments}
Una consecuencia final de la semántica de referencia puede surgir donde no lo espera. Si usa una clase R6 como el valor predeterminado de un campo, ¡se compartirá entre todas las instancias del objeto! Toma el siguiente código: queremos crear una base de datos temporal cada vez que llamamos a `TemporaryDatabase$new()`, pero el código actual siempre usa la misma ruta.
```{r}
TemporaryDatabase <- R6Class("TemporaryDatabase", list(
con = NULL,
file = TemporaryFile$new(),
initialize = function() {
self$con <- DBI::dbConnect(RSQLite::SQLite(), path = file$path)
},
finalize = function() {
DBI::dbDisconnect(self$con)
}
))
db_a <- TemporaryDatabase$new()
db_b <- TemporaryDatabase$new()
db_a$file$path == db_b$file$path
```
(Si está familiarizado con Python, esto es muy similar al problema del "argumento predeterminado mutable").
El problema surge porque `TemporaryFile$new()` se llama solo una vez cuando se define la clase `TemporaryDatabase`. Para solucionar el problema, debemos asegurarnos de que se llame cada vez que se llame a `TemporaryDatabase$new()`, es decir, debemos ponerlo en `$initialize()`:
```{r}
TemporaryDatabase <- R6Class("TemporaryDatabase", list(
con = NULL,
file = NULL,
initialize = function() {
self$file <- TemporaryFile$new()
self$con <- DBI::dbConnect(RSQLite::SQLite(), path = file$path)
},
finalize = function() {
DBI::dbDisconnect(self$con)
}
))
db_a <- TemporaryDatabase$new()
db_b <- TemporaryDatabase$new()
db_a$file$path == db_b$file$path
```
### Ejercicios
1. Cree una clase que le permita escribir una línea en un archivo específico. Debe abrir una conexión al archivo en `$initialize()`, agregar una línea usando `cat()` en `$append_line()` y cerrar la conexión en `$finalize()`.
## ¿Por qué R6? {#sec-why-r6}
\index{reference classes} \index{R6!versus reference classes}
R6 es muy similar a un sistema OO incorporado llamado **clases de referencia**, o RC para abreviar. Prefiero R6 a RC porque:
- R6 es mucho más simple. Tanto R6 como RC están construidos sobre entornos, pero mientras que R6 usa S3, RC usa S4. Esto significa que para comprender completamente RC, debe comprender cómo funciona el S4 más complicado.
- R6 tiene documentación completa en línea en <https://r6.r-lib.org>.
- R6 tiene un mecanismo más simple para la subclasificación de paquetes cruzados, que simplemente funciona sin que tengas que pensar en ello. Para RC, lea los detalles en la sección "Métodos externos; Superclases entre paquetes" de `?setRefClass`.
- RC mezcla variables y campos en la misma pila de entornos para que obtenga (`field`) y establezca (`field <<- value`) campos como valores regulares. R6 coloca los campos en un entorno separado para que obtenga (`self$field`) y establezca (`self$field <- value`) con un prefijo. El enfoque R6 es más detallado, pero me gusta porque es más explícito.
- R6 es mucho más rápido que RC. En general, la velocidad de envío del método no es importante fuera de los micropuntos de referencia. Sin embargo, RC es bastante lento y cambiar de RC a R6 condujo a una mejora sustancial del rendimiento en el paquete brillante. Para obtener más detalles, consulte `vignette("Rendimiento", "R6")`.
- RC está vinculado a R. Eso significa que si se corrigen errores, solo puede aprovechar las correcciones al solicitar una versión más nueva de R. Esto dificulta los paquetes (como los del tidyverse) que necesitan funcionar en muchos R versiones.
- Finalmente, debido a que las ideas que subyacen en R6 y RC son similares, solo requerirá una pequeña cantidad de esfuerzo adicional para aprender RC si es necesario.