-
Notifications
You must be signed in to change notification settings - Fork 0
/
s3.qmd
617 lines (457 loc) · 27 KB
/
s3.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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# S3 {#s3}
Es posible que haya notado que los resultados de su máquina tragamonedas no se ven como prometí. Sugerí que la máquina tragamonedas mostraría sus resultados así:
``` r
play()
## 0 0 DD
## $0
```
Pero la máquina actual muestra sus resultados en un formato menos bonito:
``` r
play()
## "0" "0" "DD"
## 0
```
Además, la máquina tragamonedas usa un truco para mostrar símbolos (llamamos `print` desde dentro de `play`). Como resultado, los símbolos no siguen la salida de su premio si lo guarda:
``` r
a_play <- play()
## "B" "0" "B"
a_play
## 0
```
Puede solucionar ambos problemas con el sistema S3 de R.
## El Sistema S3
S3 se refiere a un sistema de clases integrado en R. El sistema rige cómo R maneja objetos de diferentes clases. Ciertas funciones de R buscarán la clase S3 de un objeto y luego se comportarán de manera diferente en respuesta.
La función `print` es así. Cuando imprime un vector numérico, `print` mostrará un número:
``` r
num <- 1000000000
print(num)
## 1000000000
```
Pero si le da ese número a la clase S3 `POSIXct` seguida de `POSIXt`, `print` mostrará una hora:
``` r
class(num) <- c("POSIXct", "POSIXt")
print(num)
## "2001-09-08 19:46:40 CST"
```
Si usa objetos con clases, y lo hace, se encontrará con el sistema S3 de R. El comportamiento de S3 puede parecer extraño al principio, pero es fácil de predecir una vez que se familiariza con él.
El sistema S3 de R se basa en tres componentes: atributos (especialmente el atributo `class`), funciones genéricas y métodos.
## Atributos
En [Atributos](#attributes), aprendió que muchos objetos de R vienen con atributos, piezas de información adicional a las que se les da un nombre y se agregan al objeto. Los atributos no afectan los valores del objeto, pero se adhieren al objeto como un tipo de metadatos que R puede usar para manejar el objeto. Por ejemplo, un data frame almacena sus nombres de fila y columna como atributos. Los data frame también almacenan su clase, `"data.frame"`, como un atributo.
Puede ver los atributos de un objeto con `attribute`. Si ejecuta `attribute` en el data frame `mazo` que creó en [Proyecto 2: Baraja de Cartas](#cards), verá:
``` r
attributes(mazo)
## $names
## [1] "cara" "palo" "valor"
##
## $class
## [1] "data.frame"
##
## $row.names
## [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
## [20] 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
## [37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
```
R viene con muchas funciones auxiliares que le permiten establecer y acceder a los atributos más comunes utilizados en R. Ya conoce las funciones `names`, `dim` y `class`, cada una de las cuales funciona con un atributo con nombre homónimo. Sin embargo, R también tiene `row.names`, `levels` y muchas otras funciones auxiliares basadas en atributos. Puede utilizar cualquiera de estas funciones para recuperar el valor de un atributo:
``` r
row.names(mazo)
## [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13"
## [14] "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26"
## [27] "27" "28" "29" "30" "31" "32" "33" "34" "35" "36" "37" "38" "39"
## [40] "40" "41" "42" "43" "44" "45" "46" "47" "48" "49" "50" "51" "52"
```
o para cambiar el valor de un atributo:
``` r
row.names(mazo) <- 101:152
```
o para dar a un objeto un atributo completamente nuevo:
``` r
levels(mazo) <- c("level 1", "level 2", "level 3")
attributes(mazo)
## $names
## [1] "cara" "palo" "valor"
##
## $class
## [1] "data.frame"
##
## $row.names
## [1] 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
## [18] 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
## [35] 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
## [52] 152
##
## $levels
## [1] "level 1" "level 2" "level 3"
```
R es muy libre cuando se trata de atributos. Le permitirá agregar cualquier atributo que desee a un objeto (y luego generalmente los ignorará). La única vez que R se quejará es cuando una función necesita encontrar un atributo y no está allí.
Puede agregar cualquier atributo general a un objeto con `attr`; también puede usar `attr` para buscar el valor de cualquier atributo de un objeto. Veamos cómo funciona esto con `a_play`, el resultado de jugar nuestra máquina tragamonedas una vez:
``` r
a_play <- play()
a_play
## 0
attributes(a_play)
## NULL
```
`attr` toma dos argumentos: un objeto de R y el nombre de un atributo (como una cadena de caracteres). Para darle al objeto de R un atributo del nombre especificado, guarde un valor en la salida de `attr`. Démosle a `a_play` un atributo llamado `simbolos` que contiene un vector de cadenas de caracteres:
``` r
attr(a_play, "simbolos") <- c("B", "0", "B")
attributes(a_play)
## $simbolos
## [1] "B" "0" "B"
```
Para buscar el valor de cualquier atributo, dale a `attr` un objeto R y el nombre del atributo que te gustaría buscar:
``` r
attr(a_play, "simbolos")
## "B" "0" "B"
```
Si asigna un atributo a un vector atómico, como `a_play`, R generalmente mostrará el atributo debajo de los valores del vector. Sin embargo, si el atributo cambia la clase del vector, R puede mostrar toda la información en el vector de una nueva forma (como vimos con los objetos `POSIXct`):
``` r
a_play
## [1] 0
## attr(,"simbolos")
## [1] "B" "0" "B"
```
R generalmente ignorará los atributos de un objeto a menos que le des un nombre que busca una función de R, como `names` o `class`. Por ejemplo, R ignorará el atributo `simbolos` de `a_play` mientras manipulas `a_play`:
``` r
a_play + 1
## 1
## attr(,"simbolos")
## "B" "0" "B"
```
**Ejercicio 11.1 (Añade un Atributo)** Modifique `play` para devolver un premio que contenga los símbolos asociados a él como un atributo denominado `simbolos`. Elimine la llamada redundante a `print(simbolos)`:
``` r
play <- function() {
simbolos <- obt_simbolos()
print(simbolos)
puntuacion(simbolos)
}
```
*Solución.* Puede crear una nueva versión de `play` capturando la salida de `puntuacion(simbolos)` y asignándole un atributo. `play` puede devolver la versión mejorada de la salida:
``` r
play <- function() {
simbolos <- obt_simbolos()
premio <- puntuacion(simbolos)
attr(premio, "simbolos") <- simbolos
premio
}
```
Ahora `play` devuelve tanto el premio como los símbolos asociados con el premio. Puede que los resultados no se vean bonitos, pero los símbolos se quedan con el premio cuando lo copiamos a un nuevo objeto. Podemos trabajar en ordenar la pantalla en un minuto:
``` r
play()
## [1] 0
## attr(,"simbolos")
## [1] "B" "BB" "0"
b_play <- play()
b_play
## [1] 0
## attr(,"simbolos")
## [1] "0" "B" "0"
```
También puede generar un premio y establecer sus atributos en un solo paso con la función `structure`. `structure` crea un objeto con un conjunto de atributos. El primer argumento de `structure` debe ser un objeto de R o un conjunto de valores, y los argumentos restantes deben ser atributos con nombre para que `structure` se agregue al objeto. Puede dar a estos argumentos cualquier nombre de argumento que desee. `structure` agregará los atributos al objeto bajo los nombres que proporcione como nombres de argumento:
``` r
play <- function() {
simbolos <- get_simbolos()
structure(puntuacion(simbolos), simbolos = simbolos)
}
c_play <- play()
c_play
## 0
## attr(,"simbolos")
## "0" "BB" "B"
```
Ahora que su salida `play` contiene un atributo `simbolos`, ¿qué puede hacer con él? Puede escribir sus propias funciones que busquen y usen el atributo. Por ejemplo, la siguiente función buscará el atributo `simbolos` de `a_play` y lo usará para mostrar `a_play` de una manera bonita. Usaremos esta función para mostrar los resultados de nuestra tragamonedas, así que tomemos un momento para estudiar lo que hace:
``` r
mostrat_tragamoneda <- function(premio){
# extraer simbolos
simbolos <- attr(premio, "simbolos")
# colapsar símbolos en una sola cadena de caracteres
simbolos <- paste(simbolos, collapse = " ")
# combinar símbolo con premio como una cadena de caracteres
# \n es una secuencia de escape especial para una nueva línea (i.e. return or enter)
texto <- paste(simbolos, premio, sep = "\n$")
# mostrar cadena de caracteres en la consola sin comillas
cat(texto)
}
mostrat_tragamoneda(a_play)
## B 0 B
## $0
```
La función espera un objeto como `a_play` que tiene tanto un valor numérico como un atributo de `simbolos`. La primera línea de la función buscará el valor del atributo `simbolos` y lo guardará como un objeto llamado `simbolos`. Hagamos un objeto `simbolos` de ejemplo para que podamos ver lo que hace el resto de la función. Podemos usar el atributo `simbolos` de `a_play` para hacer el trabajo. `simbolos` será un vector de cadenas de tres caracteres:
``` r
simbolos <- attr(a_play, "simbolos")
simbolos
## "B" "0" "B"
```
A continuación, `mostrat_tragamoneda` usa `paste` para colapsar las tres cadenas en `simbolos` en una cadena de un solo carácter. `paste` colapsa un vector de cadenas de caracteres en una sola cadena cuando le das el argumento `collapse`. `paste` usará el valor de `collapse` para separar las cadenas anteriormente distintas. Por lo tanto, `simbolos` se convierte en `B 0 B` las tres cadenas separadas por un espacio:
``` r
simbolos <- paste(simbolos, collapse = " ")
simbolos
## "B 0 B"
```
Nuestra función luego usa `paste` de una nueva forma para combinar `simbolos` con el valor de `premio`. `paste` combina objetos separados en una cadena de caracteres cuando le das un argumento `sep`. Por ejemplo, aquí `paste` combinará la cadena en `simbolos`, `B 0 B`, con el número en `premio`, 0. `paste` usará el valor del argumento `sep` para separar las entradas en el cadena nueva. Aquí, ese valor es `\n$`, por lo que nuestro resultado se verá como `"B 0 B\n$0"`:
``` r
premio <- a_play
texto <- paste(simbolos, premio, sep = "\n$")
texto
## "B 0 B\n$0"
```
La última línea de `mostrat_tragamoneda` llama a `cat` en la nueva cadena. `cat` es como `print`; muestra su entrada en la línea de comando. Sin embargo, `cat` no encierra su salida entre comillas. `cat` también reemplaza cada `\n` con una nueva línea o salto de línea. El resultado es lo que vemos. Tenga en cuenta que se ve exactamente como sugerí que nuestra salida `play` debería verse en [Programas](#programs):
``` r
cat(texto)
## B 0 B
## $0
```
Puedes usar `mostrat_tragamoneda` para limpiar manualmente la salida de `play`:
``` r
mostrat_tragamoneda(play())
## C B 0
## $2
mostrat_tragamoneda(play())
## 7 0 BB
## $0
```
Este método de limpieza de la salida requiere que intervengas manualmente en tu sesión de R (para llamar a `mostrat_tragamoneda`). Hay una función que puede usar para limpiar automáticamente la salida de `play` cada vez que se muestra. Esta función es `print`, y es una *función genérica*.
## Funciones Genéricas
R usa `print` con más frecuencia de lo que piensas; R llama a `print` cada vez que muestra un resultado en la ventana de su consola. Esta llamada ocurre en segundo plano, por lo que no la nota; pero la llamada explica cómo la salida llega a la ventana de la consola (recuerde que `print` siempre imprime su argumento en la ventana de la consola). Esta llamada `print` también explica por qué la salida de `print` siempre coincide con lo que ve cuando muestra un objeto en la línea de comando:
``` r
print(pi)
## 3.141593
pi
## 3.141593
print(head(mazo))
## cara palo valor
## rey picas 13
## reina picas 12
## jota picas 11
## diez picas 10
## nueve picas 9
## ocho picas 8
head(mazo)
## cara palo valor
## rey picas 13
## reina picas 12
## jota picas 11
## diez picas 10
## nueve picas 9
## ocho picas 8
print(play())
## 5
## attr(,"simbolos")
## "B" "BB" "B"
play()
## 5
## attr(,"simbolos")
## "B" "BB" "B"
```
Puede cambiar la forma en que R muestra la salida de su maquina tragamonedas reescribiendo `print` para que se vea como `mostrat_tragamoneda`. Entonces R mostraría la salida en el formato que hemos creado. Sin embargo, este método tendría efectos secundarios negativos. No desea que R llame a `mostrat_tragamoneda` cuando muestre un data frame, un vector numérico o cualquier otro objeto.
Afortunadamente, `print` no es una función normal; es una función *genérica*. Esto significa que `print` está escrito de una manera que le permite hacer cosas diferentes en casos diferentes. Ya has visto este comportamiento en acción (aunque es posible que no te hayas dado cuenta). `print` hizo una cosa cuando miramos la versión sin clase de `num`:
``` r
num <- 1000000000
print(num)
## 1000000000
```
y una cosa diferente cuando le dimos a `num` una clase:
``` r
class(num) <- c("POSIXct", "POSIXt")
print(num)
## "2001-09-08 19:46:40 CST"
```
Eche un vistazo al código dentro de `print` para ver cómo lo hace. Puede imaginar que print busca el atributo de clase de su entrada y luego usa un árbol +if+ para elegir qué salida mostrar. Si esto te ocurrió, ¡buen trabajo! `print` hace algo muy similar, pero mucho más simple.
## Métodos
Cuando llamas a `print`, `print` llama a una función especial, `UseMethod`:
``` r
print
## function (x, ...)
## UseMethod("print")
## <bytecode: 0x7ffee4c62f80>
## <environment: namespace:base>
```
`UseMethod` examina la clase de la entrada que proporcionas para el primer argumento de `print` y luego pasa todos tus argumentos a una nueva función diseñada para manejar esa clase de entrada. Por ejemplo, cuando le das a `print` un objeto POSIXct, `UseMethod` pasará todos los argumentos de `print` a `print.POSIXct`. R luego ejecutará `print.POSIXct` y devolverá los resultados:
``` r
print.POSIXct
## function (x, ...)
## {
## max.print <- getOption("max.print", 9999L)
## if (max.print < length(x)) {
## print(format(x[seq_len(max.print)], usetz = TRUE), ...)
## cat(" [ reached getOption(\"max.print\") -- omitted",
## length(x) - max.print, "entries ]\n")
## }
## else print(format(x, usetz = TRUE), ...)
## invisible(x)
## }
## <bytecode: 0x7fa948f3d008>
## <environment: namespace:base>
```
Si le das a `print` un objeto de factor, `UseMethod` pasará todos los argumentos de `print` a `print.factor`. R luego ejecutará `print.factor` y devolverá los resultados:
``` r
print.factor
## function (x, quote = FALSE, max.levels = NULL, width = getOption("width"),
## ...)
## {
## ord <- is.ordered(x)
## if (length(x) == 0L)
## cat(if (ord)
## "ordered"
## ...
## drop <- n > maxl
## cat(if (drop)
## paste(format(n), ""), T0, paste(if (drop)
## c(lev[1L:max(1, maxl - 1)], "...", if (maxl > 1) lev[n])
## else lev, collapse = colsep), "\n", sep = "")
## }
## invisible(x)
## }
## <bytecode: 0x7fa94a64d470>
## <environment: namespace:base>
```
`print.POSIXct` y `print.factor` se denominan *métodos* de `print`. Por sí mismos, `print.POSIXct` y `print.factor` funcionan como funciones regulares de R. Sin embargo, cada uno fue escrito específicamente para que 'UseMethod' pudiera llamarlo para manejar una clase específica de entrada de `print`.
Tenga en cuenta que `print.POSIXct` y `print.factor` hacen dos cosas diferentes (también tenga en cuenta que compendié la mitad de `print.factor`: es una función larga). Así es como `print` se las arregla para hacer diferentes cosas en diferentes casos. `print` llama a `UseMethod`, que llama a un método especializado basado en la clase del primer argumento de `print`.
Puede ver qué métodos existen para una función genérica llamando a `methods` en la función. Por ejemplo, `print` tiene casi 200 métodos (lo que le da una idea de cuántas clases existen en R):
``` r
methods(print)
## [1] print.acf*
## [2] print.anova
## [3] print.aov*
## ...
## [176] print.xgettext*
## [177] print.xngettext*
## [178] print.xtabs*
##
## Nonvisible functions are asterisked
```
Este sistema de funciones genéricas, métodos y despacho basado en clases se conoce como S3 porque se originó en la tercera versión de S, el lenguaje de programación que se convertiría en S-PLUS y R. Muchas funciones comunes de R son genéricas de S3 que funcionan con un conjunto de métodos de clase. Por ejemplo, `summary` y `head` también llaman a `UseMethod`. Las funciones más básicas, como `c`, `+`, `-`, `<` y otras también se comportan como funciones genéricas, aunque llaman a `.primitive` en lugar de `UseMethod`.
El sistema S3 permite que las funciones de R se comporten de diferentes maneras para diferentes clases. Puede usar S3 para formatear la salida de su tragamonedas. Primero, dé a su salida su propia clase. Luego escriba un método de impresión para esa clase. Para hacer esto de manera eficiente, necesitará saber un poco acerca de cómo `UseMethod` selecciona una función de método para usar.
### Selección de Método
`UseMethod` utiliza un sistema muy simple para emparejar métodos con funciones.
Cada método S3 tiene un nombre de dos partes. La primera parte del nombre se referirá a la función con la que trabaja el método. La segunda parte se referirá a la clase. Estas dos partes estarán separadas por un punto. Entonces, por ejemplo, el método de `print` que funciona con funciones se llamará `print.function`. El método de `summary` que trabaja con matrices se llamará `summary.matrix`. Y así sucesivamente.
Cuando `UseMethod` necesita llamar a un método, busca una función de R con el nombre de estilo S3 correcto. La función no tiene que ser especial de ninguna manera; solo necesita tener el nombre correcto.
Puede participar en este sistema escribiendo su propia función y dándole un nombre de estilo S3 válido. Por ejemplo, demos a `a_play` una clase propia. No importa cómo llames a la clase; R almacenará cualquier cadena de caracteres en el atributo de clase:
``` r
class(a_play) <- "tragamonedas"
```
Ahora escribamos un método de impresión S3 para la clase +tragamonedas+. El método no necesita hacer nada especial, ni siquiera necesita imprimir `a_play`. Pero *sí* necesita llamarse `print.tragamonedas`; de lo contrario, `UseMethod` no lo encontrará. El método también debería tomar los mismos argumentos que `print`; de lo contrario, R dará un error cuando intente pasar los argumentos a `print.tragamonedas`:
``` r
args(print)
## function (x, ...)
## NULL
print.tragamonedas <- function(x, ...) {
cat("Estoy usando el método print.tragamonedas")
}
```
¿Funciona nuestro método? Sí, y no solo eso; R usa el método de impresión para mostrar el contenido de `a_play`. Este método no es muy útil, así que voy a eliminarlo. Tendrás la oportunidad de escribir uno mejor en un minuto:
``` r
print(a_play)
## Estoy usando el método print.tragamonedas
a_play
## Estoy usando el método print.tragamonedas
rm(print.tragamonedas)
```
Algunos objetos de R tienen múltiples clases. Por ejemplo, la salida de `Sys.time` tiene dos clases. ¿Qué clase usará `UseMethod` para encontrar un método de impresión?
``` r
ahora <- Sys.time()
attributes(ahora)
## $class
## [1] "POSIXct" "POSIXt"
```
`UseMethod` primero buscará un método que coincida con la primera clase listada en el vector de clase del objeto. Si `UseMethod` no puede encontrar uno, buscará el método que coincida con la segunda clase (y así sucesivamente si hay más clases en el vector de clase de un objeto).
Si le das a `print` un objeto cuya clase o clases no tienen un método de impresión, `UseMethod` llamará a `print.default`, un método especial escrito para manejar casos generales.
Usemos este sistema para escribir un mejor método de impresión para la salida de la máquina tragamonedas.
Ejercicio 11.2 (Hacer un Método de print) Escriba un nuevo método de print para la clase de tragamonedas. El método debería llamar a `mostrat_tragamoneda` para devolver una salida de máquina tragamonedas bien formateada.
¿Qué nombre debe usar para este método?
*Solución.* Es sorprendentemente fácil escribir un buen método `print.tragamonedas` porque ya hicimos todo el trabajo duro cuando escribimos `mostrat_tragamoneda`. Por ejemplo, el siguiente método funcionará. Solo asegúrese de que el método se llame `print.tragamonedas` para que `UseMethod` pueda encontrarlo, y asegúrese de que toma los mismos argumentos que `print` para que `UseMethod` pueda pasar esos argumentos a `print.tragamonedas` sin ningún problema:
``` r
print.tragamonedas <- function(x, ...) {
mostrat_tragamoneda(x)
}
```
Ahora R usará automáticamente `mostrat_tragamoneda` para mostrar objetos de clase +tragamonedas+ (y solo objetos de clase "tragamonedas"):
``` r
a_play
## B 0 B
## $0
```
Asegurémonos de que cada salida de la máquina tragamonedas tenga la clase `tragamonedas`.
**Ejercicio 11.3 (Añadir una Clase)** Modifique la función `play` para que asigne `tragamonedas` al atributo `class` de su salida:
``` r
play <- function() {
simbolos <- get_simbolos()
structure(puntuacion(simbolos), simbolos = simbolos)
}
```
*Solución.* Puede establecer el atributo `class` de la salida al mismo tiempo que establece el atributo +simbolos+. Simplemente agregue `class = "tragamonedas"` a la llamada `structure`:
``` r
play <- function() {
simbolos <- get_simbolos()
structure(puntuacion(simbolos), simbolos = simbolos, class = "tragamonedas")
}
```
Ahora cada uno de nuestras salidas de `play` tendrá la clase `tragamonedas`:
``` r
class(play())
## "tragamonedas"
```
Como resultado, R los mostrará en el formato de máquina tragamonedas correcto:
``` r
play()
## BB BB BBB
## $5
play()
## BB 0 0
## $0
```
## Clases
Puede usar el sistema S3 para crear una nueva clase sólida de objetos en R. Luego, R tratará los objetos de su clase de manera consistente y sensata. Para hacer una clase:
- Elija un nombre para su clase.
- Asigne a cada instancia de su clase un atributo +class+.
- Escriba métodos de clase para cualquier función genérica que pueda usar objetos de su clase.
Muchos paquetes de R se basan en clases que se han creado de manera similar. Si bien este trabajo es simple, puede que no sea fácil. Por ejemplo, considere cuántos métodos existen para clases predefinidas.
Puede llamar a `methods` en una clase con el argumento `class`, que toma una cadena de caracteres. `methods` devolverá todos los métodos escritos para la clase. Tenga en cuenta que `methods` no podrá mostrarle los métodos que vienen en un paquete R descargado:
``` r
methods(class = "factor")
## [1] [.factor [[.factor
## [3] [[<-.factor [<-.factor
## [5] all.equal.factor as.character.factor
## [7] as.data.frame.factor as.Date.factor
## [9] as.list.factor as.logical.factor
## [11] as.POSIXlt.factor as.vector.factor
## [13] droplevels.factor format.factor
## [15] is.na<-.factor length<-.factor
## [17] levels<-.factor Math.factor
## [19] Ops.factor plot.factor*
## [21] print.factor relevel.factor*
## [23] relist.factor* rep.factor
## [25] summary.factor Summary.factor
## [27] xtfrm.factor
##
## Nonvisible functions are asterisked
```
Esta salida indica cuánto trabajo se requiere para crear una clase robusta y de buen comportamiento. Por lo general, necesitará escribir un método de `class` para cada operación básica de R.
Considere dos desafíos que enfrentará de inmediato. Primero, R descarta atributos (como `class`) cuando combina objetos en un vector:
``` r
play1 <- play()
play1
## B BBB BBB
## $5
play2 <- play()
play2
## 0 B 0
## $0
c(play1, play2)
## [1] 5 0
```
Aquí, R deja de usar `print.tragamonedas` para mostrar el vector porque el vector `c(play1, play2)` ya no tiene un atributo +class+ "tragamonedas".
A continuación, R eliminará los atributos de un objeto (como `class`) cuando subjunte el objeto:
``` r
play1[1]
## [1] 5
```
Puede evitar este comportamiento escribiendo un método `c.tragamonedas` y un método `[.tragamonedas`, pero luego se acumularán dificultades rápidamente. ¿Cómo combinaría los atributos de `simbolos` de múltiples jugadas en un vector de atributos de símbolos? ¿Cómo cambiarías `print.tragamonedas` para manejar vectores de salidas? Estos desafíos están abiertos para que los explores. Sin embargo, normalmente no tendrá que intentar este tipo de programación a gran escala como científico de datos.
En nuestro caso, es muy útil dejar que los objetos `tragamonedas` vuelvan a tener valores de premios únicos cuando combinamos grupos de ellos en un vector.
## S3 y Depuración
S3 puede ser molesto si está tratando de comprender las funciones de R. Es difícil saber qué hace una función si su cuerpo de código contiene una llamada a `UseMethod`. Ahora que sabe que `UseMethod` llama a un método específico de clase, puede buscar y examinar el método directamente. Será una función cuyo nombre siga la sintaxis `<function.class>`, o posiblemente `<function.default>`. También puede usar la función `methods` para ver qué métodos están asociados con una función o una clase.
## S4 y R5
R también contiene otros dos sistemas que crean un comportamiento específico de clase. Estos se conocen como S4 y R5 (o clases de referencia). Cada uno de estos sistemas es mucho más difícil de usar que S3 y quizás, como consecuencia, más raro. Sin embargo, ofrecen garantías que S3 no ofrece. Si desea obtener más información sobre estos sistemas, incluido cómo escribir y usar sus propias funciones genéricas, le recomiendo el libro [*Advanced R Programming*](http://adv-r.had.co.nz/) de Hadley Wickham.
## Resumen
Los valores no son el único lugar para almacenar información en R, y las funciones no son la única forma de crear un comportamiento único. También puede hacer ambas cosas con el sistema S3 de R. El sistema S3 proporciona una forma sencilla de crear comportamientos específicos de objetos en R. En otras palabras, es la versión de programación orientada a objetos (OOP) de R. El sistema se implementa mediante funciones genéricas. Estas funciones examinan el atributo de clase de su entrada y llaman a un método específico de clase para generar salida. Muchos métodos de S3 buscarán y utilizarán información adicional que se almacena en los atributos de un objeto. Muchas funciones comunes de R son genéricas de S3.
El sistema S3 de R es más útil para las tareas de informática que para las tareas de ciencia de datos, pero comprender S3 puede ayudarlo a solucionar problemas en su trabajo en R como científico de datos
Ahora sabe bastante sobre cómo escribir código de R que realiza tareas personalizadas, pero ¿cómo podría repetir estas tareas? Como científico de datos, a menudo repetirá tareas, a veces miles o incluso millones de veces. ¿Por qué? Porque la repetición te permite simular resultados y estimar probabilidades. [Bucles](#loops) te mostrará cómo automatizar la repetición con las funciones `for` y `while` de R. Usará `for` para simular varios juegos de máquinas tragamonedas y para calcular la tasa de pago de su máquina tragamonedas.