-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathS4.qmd
629 lines (409 loc) · 29.8 KB
/
S4.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
618
619
620
621
622
623
624
625
626
627
628
629
# S4 {#sec-s4}
```{r setup, include = FALSE}
source("common.R")
source("emoji.R")
# Hide annoying output
setMethod <- function(...) invisible(methods::setMethod(...))
setGeneric <- function(...) invisible(methods::setGeneric(...))
setValidity <- function(...) invisible(methods::setValidity(...))
code <- function(...) paste0("`", ..., "`")
```
## Introducción
S4 proporciona un enfoque formal para la programación orientada a objetos funcional. Las ideas subyacentes son similares a S3 (el tema del @sec-s3), pero la implementación es mucho más estricta y utiliza funciones especializadas para crear clases (`setClass()`), genéricos (`setGeneric()`) y métodos (`setMethod()`). Además, S4 proporciona herencia múltiple (es decir, una clase puede tener varios padres) y envío múltiple (es decir, el envío del método puede usar la clase de varios argumentos).
Un nuevo componente importante de S4 es la **ranura**, un componente con nombre del objeto al que se accede mediante el operador de subconjunto especializado `@`. El conjunto de ranuras y sus clases forma una parte importante de la definición de una clase S4.
### Estructura {.unnumbered}
- La @sec-s4-basics brinda una descripción general rápida de los componentes principales de S4: clases, genéricos y métodos.
- La @sec-s4-classes se sumerge en los detalles de las clases de S4, incluidos prototipos, constructores, ayudantes y validadores.
- La @sec-s4-generics le muestra cómo crear nuevos genéricos S4 y cómo proporcionar métodos a esos genéricos. También aprenderá acerca de las funciones de acceso que están diseñadas para permitir que los usuarios inspeccionen y modifiquen las ranuras de objetos de manera segura.
- La @sec-s4-dispatch se sumerge en los detalles completos del envío de métodos en S4. La idea básica es simple, pero rápidamente se vuelve más compleja una vez que se combinan la herencia múltiple y el envío múltiple.
- La @sec-s4-s3 analiza la interacción entre S4 y S3 y le muestra cómo usarlos juntos.
### Aprendiendo más {.unnumbered}
Al igual que los otros capítulos de OO, el enfoque aquí será cómo funciona S4, no cómo implementarlo de manera más efectiva. Si desea usarlo en la práctica, hay dos desafíos principales:
- No hay una referencia que responda a todas sus preguntas sobre S4.
- La documentación integrada de R a veces choca con las mejores prácticas de la comunidad.
A medida que avanza hacia un uso más avanzado, deberá reunir la información necesaria leyendo detenidamente la documentación, haciendo preguntas sobre StackOverflow y realizando experimentos. Algunas recomendaciones:
- La comunidad de bioconductores es un usuario de S4 desde hace mucho tiempo y ha producido gran parte del mejor material sobre su uso efectivo. Comience con [Clases y métodos de S4](https://bioconductor.org/help/course-materials/2017/Zurich/S4-classes-and-methods.html) impartido por Martin Morgan y Hervé Pagès, o busque uno más nuevo versión en \[Materiales del curso de bioconductores\] (https://bioconductor.org/help/course-materials/).
Martin Morgan es un ex miembro de R-core y líder del proyecto de Bioconductor. Es un experto mundial en el uso práctico de S4 y recomiendo leer todo lo que ha escrito al respecto, comenzando con las preguntas que ha respondido en [stackoverflow](https://stackoverflow.com/users/547331/martin-morgan?tab=answers).
- John Chambers es el autor del sistema S4 y proporciona una descripción general de su motivación y contexto histórico en *Programación orientada a objetos, programación funcional y R* [@chambers-2014]. Para una exploración más completa de S4, consulte su libro *Software for Data Analysis* [@s4da].
### Requisitos previos {.unnumbered}
Todas las funciones relacionadas con S4 viven en el paquete de métodos. Este paquete siempre está disponible cuando ejecuta R de forma interactiva, pero es posible que no esté disponible cuando ejecuta R en modo por lotes, es decir, desde `Rscript`[^s4-1]. Por esta razón, es una buena idea llamar a `library(methods)` siempre que use S4. Esto también indica al lector que utilizará el sistema de objetos S4.
[^s4-1]: Esta es una peculiaridad histórica introducida porque el paquete de métodos solía tardar mucho tiempo en cargarse y `Rscript` está optimizado para una invocación rápida de la línea de comandos.
```{r}
library(methods)
```
## Lo escencial {#sec-s4-basics}
Comenzaremos con una descripción general rápida de los componentes principales de S4. Una clase de S4 se define llamando a `setClass()` con el nombre de la clase y una definición de sus ranuras, y los nombres y clases de los datos de la clase:
```{r}
setClass("Person",
slots = c(
name = "character",
age = "numeric"
)
)
```
Una vez que se define la clase, puede construir nuevos objetos a partir de ella llamando a `new()` con el nombre de la clase y un valor para cada ranura:
```{r}
john <- new("Person", name = "John Smith", age = NA_real_)
```
```{=tex}
\index{"@}
\index{slot()}
```
\index{subsetting!S4} \index{S4!subsetting}
Dado un objeto S4, puede ver su clase con `is()` y acceder a las ranuras con `@` (equivalente a `$`) y `slot()` (equivalente a `[[`):
```{r}
is(john)
john@name
slot(john, "age")
```
En general, solo debe usar `@` en sus métodos. Si está trabajando con la clase de otra persona, busque funciones de **accesorio** que le permitan establecer y obtener valores de ranura de forma segura. Como desarrollador de una clase, también debe proporcionar sus propias funciones de acceso. Los accesores suelen ser genéricos de S4 que permiten que varias clases compartan la misma interfaz externa.
Aquí crearemos un setter y getter para el espacio `age` creando primero genéricos con `setGeneric()`:
```{r}
setGeneric("age", function(x) standardGeneric("age"))
setGeneric("age<-", function(x, value) standardGeneric("age<-"))
```
Y luego definiendo métodos con `setMethod()`:
```{r}
setMethod("age", "Person", function(x) x@age)
setMethod("age<-", "Person", function(x, value) {
x@age <- value
x
})
age(john) <- 50
age(john)
```
Si está utilizando una clase S4 definida en un paquete, puede obtener ayuda con `class?Person`. Para obtener ayuda para un método, coloque `?` delante de una llamada (por ejemplo, `?age(john)`) y `?` usará la clase de los argumentos para averiguar qué archivo de ayuda necesita.
Finalmente, puede usar las funciones de sloop para identificar los objetos y genéricos de S4 que se encuentran en la naturaleza:
```{r}
sloop::otype(john)
sloop::ftype(age)
```
### Ejercicios
1. `lubridate::period()` devuelve una clase S4. ¿Qué ranuras tiene? ¿Qué clase es cada ranura? ¿Qué accesorios proporciona?
2. ¿De qué otras formas puede encontrar ayuda para un método? Lea `?"?"` y resuma los detalles.
## Clases {#sec-s4-classes}
\index{classes!S4} \index{S4!classes} \index{setClass()}
Para definir una clase S4, llama a `setClass()` con tres argumentos:
- La clase **nombre**. Por convención, los nombres de clase de S4 usan `UpperCamelCase`.
- Un vector de caracteres con nombre que describe los nombres y las clases de las **ranuras** (campos). Por ejemplo, una persona puede estar representada por un nombre de personaje y una edad numérica: `c(name = "personaje", age = "numérico")`. La pseudoclase `ANY` permite que una ranura acepte objetos de cualquier tipo.
- Un **prototipo**, una lista de valores predeterminados para cada ranura. Técnicamente, el prototipo es opcional[^s4-2], pero siempre debes proporcionarlo.
[^s4-2]: `?setClass` recomienda que evite el argumento `prototype`, pero esto generalmente se considera un mal consejo.
El siguiente código ilustra los tres argumentos mediante la creación de una clase `Person` con el carácter `name` y las ranuras numéricas `age`.
```{r, cache = FALSE}
setClass("Person",
slots = c(
name = "character",
age = "numeric"
),
prototype = list(
name = NA_character_,
age = NA_real_
)
)
me <- new("Person", name = "Hadley")
str(me)
```
### Herencia {#sec-s4-inheritance}
\index{S4!inheritance} \index{inheritance!S4}
Hay otro argumento importante para `setClass()`: `contains`. Esto especifica una clase (o clases) de las que heredar las ranuras y el comportamiento. Por ejemplo, podemos crear una clase `Employee` que herede de la clase `Person`, agregando un espacio adicional que describa su `boss`.
```{r}
setClass("Employee",
contains = "Person",
slots = c(
boss = "Person"
),
prototype = list(
boss = new("Person")
)
)
str(new("Employee"))
```
`setClass()` tiene otros 9 argumentos pero están en desuso o no se recomiendan.
### Introspección {#sec-s4-introspection}
\index{S4!introspection} \index{is()}
Para determinar de qué clases hereda un objeto, usa `is()`:
```{r}
is(new("Person"))
is(new("Employee"))
```
Para probar si un objeto hereda de una clase específica, usa el segundo argumento de `is()`:
```{r}
is(john, "Person")
```
### Redefinición
En la mayoría de los lenguajes de programación, la definición de clase ocurre en tiempo de compilación y la construcción de objetos ocurre más tarde, en tiempo de ejecución. En R, sin embargo, tanto la definición como la construcción ocurren en tiempo de ejecución. Cuando llamas a `setClass()`, estás registrando una definición de clase en una variable global (oculta). Al igual que con todas las funciones de modificación de estado, debe usar `setClass()` con cuidado. Es posible crear objetos no válidos si redefine una clase después de haber creado una instancia de un objeto:
```{r, error = TRUE}
setClass("A", slots = c(x = "numeric"))
a <- new("A", x = 10)
setClass("A", slots = c(a_different_slot = "numeric"))
a
```
Esto puede causar confusión durante la creación interactiva de nuevas clases. (Las clases R6 tienen el mismo problema, como se describe en la @sec-r6-important-methods.)
### Ayudante
\index{helpers!S4} \index{constructors!S4} \index{S4!helpers} \index{new()}
`new()` es un constructor de bajo nivel adecuado para que lo use usted, el desarrollador. Las clases orientadas al usuario siempre deben combinarse con un asistente fácil de usar. Un ayudante siempre debe:
- Tener el mismo nombre que la clase, p. `myclass()`.
- Tenga una interfaz de usuario cuidadosamente diseñada con valores predeterminados cuidadosamente seleccionados y conversiones útiles.
- Cree mensajes de error cuidadosamente elaborados y adaptados a un usuario final.
- Termine llamando a `methods::new()`.
La clase `Person` es tan simple que un ayudante es casi superfluo, pero podemos usarlo para definir claramente el contrato: `age` es opcional pero `name` es obligatorio. También forzaremos la edad a un doble para que el ayudante también funcione cuando se pasa un número entero.
```{r}
Person <- function(name, age = NA) {
age <- as.double(age)
new("Person", name = name, age = age)
}
Person("Hadley")
```
### Validador
\index{validators!S4} \index{S4!validators} \index{setValidity()}
El constructor verifica automáticamente que las ranuras tengan las clases correctas:
```{r, error = TRUE}
Person(mtcars)
```
Deberá implementar verificaciones más complicadas (es decir, verificaciones que involucren longitudes o múltiples ranuras) usted mismo. Por ejemplo, es posible que queramos dejar en claro que la clase Person es una clase vectorial y puede almacenar datos sobre varias personas. Eso no está claro actualmente porque `@name` y `@age` pueden tener diferentes longitudes:
```{r}
Person("Hadley", age = c(30, 37))
```
Para hacer cumplir estas restricciones adicionales, escribimos un validador con `setValidity()`. Toma una clase y una función que devuelve `TRUE` si la entrada es válida y, de lo contrario, devuelve un vector de caracteres que describe los problemas:
```{r}
setValidity("Person", function(object) {
if (length(object@name) != length(object@age)) {
"@name and @age must be same length"
} else {
TRUE
}
})
```
Ahora ya no podemos crear un objeto no válido:
```{r, error = TRUE}
Person("Hadley", age = c(30, 37))
```
NB: El método de validez solo es llamado automáticamente por `new()`, por lo que aún puede crear un objeto no válido modificándolo:
```{r}
alex <- Person("Alex", age = 30)
alex@age <- 1:10
```
\index{validObject()}
Puedes verificar explícitamente la validez tú mismo llamando a `validObject()`:
```{r, error = TRUE}
validObject(alex)
```
En la @sec-accessors, usaremos `validObject()` para crear accesores que no pueden crear objetos no válidos.
### Ejercicios
1. Extiende la clase Person con campos para que coincidan con `utils::person()`. Piense en qué ranuras necesitará, qué clase debe tener cada ranura y qué necesitará verificar en su método de validez.
2. ¿Qué sucede si define una nueva clase S4 que no tiene ranuras? (Sugerencia: lea acerca de las clases virtuales en `?setClass`).
3. Imagine que iba a volver a implementar factores, fechas y marcos de datos en S4. Esboce las llamadas `setClass()` que usaría para definir las clases. Piense en `slots` y `prototypes` apropiados.
## Genéricos y métodos {#sec-s4-generics}
\index{S4!generics} \index{generics!S4} \index{setGeneric()} \index{standardGeneric()}
El trabajo de un genérico es realizar el envío de métodos, es decir, encontrar la implementación específica para la combinación de clases pasadas al genérico. Aquí aprenderá a definir métodos y genéricos de S4; luego, en la siguiente sección, exploraremos con precisión cómo funciona el envío de métodos de S4.
Para crear un nuevo S4 genérico, llame a `setGeneric()` con una función que llame a `standardGeneric()`:
```{r}
setGeneric("myGeneric", function(x) standardGeneric("myGeneric"))
```
Por convención, los nuevos genéricos de S4 deben usar `lowerCamelCase`.
Es una mala práctica usar `{}` en el genérico, ya que desencadena un caso especial que es más costoso y, en general, es mejor evitarlo.
```{r}
# Don't do this!
setGeneric("myGeneric", function(x) {
standardGeneric("myGeneric")
})
```
### Firma
\index{signature}
Al igual que `setClass()`, `setGeneric()` tiene muchos otros argumentos. Solo hay uno que debe conocer: `signature`. Esto le permite controlar los argumentos que se utilizan para el envío de métodos. Si no se proporciona `signature`, se utilizan todos los argumentos (excepto `...`). Ocasionalmente, es útil eliminar argumentos del envío. Esto le permite requerir que los métodos proporcionen argumentos como `verbose = TRUE` o `quiet = FALSE`, pero no toman parte en el envío.
```{r}
setGeneric("myGeneric",
function(x, ..., verbose = TRUE) standardGeneric("myGeneric"),
signature = "x"
)
```
### Métodos
\index{setMethod()}
\index{methods!S4} \index{S4!methods}
Un genérico no es útil sin algunos métodos, y en S4 se definen métodos con `setMethod()`. Hay tres argumentos importantes: el nombre del genérico, el nombre de la clase y el método en sí.
```{r}
setMethod("myGeneric", "Person", function(x) {
# method implementation
})
```
Más formalmente, el segundo argumento de `setMethod()` se llama **signature**. En S4, a diferencia de S3, la firma puede incluir múltiples argumentos. Esto hace que el envío de métodos en S4 sea sustancialmente más complicado, pero evita tener que implementar el envío doble como un caso especial. Hablaremos más sobre el envío múltiple en la siguiente sección. `setMethod()` tiene otros argumentos, pero nunca debes usarlos.
Para listar todos los métodos que pertenecen a un genérico, o que están asociados con una clase, use `methods("generic")` o `methods(class = "class")`; para encontrar la implementación de un método específico, use `selectMethod("generic", "class")`.
### Mostrar método {#sec-show-method}
\index{show()}
\index{S4!show()@\texttt{show()}}
El método S4 más comúnmente definido que controla la impresión es `show()`, que controla cómo aparece el objeto cuando se imprime. Para definir un método para un genérico existente, primero debe determinar los argumentos. Puede obtenerlos de la documentación o mirando los `args()` del genérico:
```{r}
args(getGeneric("show"))
```
Nuestro método show necesita tener un solo argumento `object`:
```{r}
setMethod("show", "Person", function(object) {
cat(is(object)[[1]], "\n",
" Name: ", object@name, "\n",
" Age: ", object@age, "\n",
sep = ""
)
})
john
```
### Accesorios {#sec-accessors}
\index{S4!accessors}
Las ranuras deben considerarse un detalle de implementación interna: pueden cambiar sin previo aviso y el código de usuario debe evitar acceder a ellas directamente. En su lugar, todas las ranuras accesibles para el usuario deben ir acompañadas de un par de **accesorios**. Si la ranura es única para la clase, esto puede ser solo una función:
```{r}
person_name <- function(x) x@name
```
Sin embargo, por lo general, definirá un genérico para que varias clases puedan usar la misma interfaz:
```{r}
setGeneric("name", function(x) standardGeneric("name"))
setMethod("name", "Person", function(x) x@name)
name(john)
```
Si la ranura también se puede escribir, debe proporcionar una función de establecimiento. Siempre debe incluir `validObject()` en el setter para evitar que el usuario cree objetos no válidos.
```{r, error = TRUE}
setGeneric("name<-", function(x, value) standardGeneric("name<-"))
setMethod("name<-", "Person", function(x, value) {
x@name <- value
validObject(x)
x
})
name(john) <- "Jon Smythe"
name(john)
name(john) <- letters
```
(Si la notación `name<-` no le resulta familiar, revise la @sec-function-forms.)
### Ejercicios
1. Agregue accesores `age()` para la clase `Person`.
2. En la definición de genérico, ¿por qué es necesario repetir dos veces el nombre del genérico?
3. ¿Por qué el método `show()` definido en la @sec-show-method usa `is(object)[[1]]`? (Sugerencia: intente imprimir la subclase de empleado).
4. ¿Qué pasa si defines un método con nombres de argumentos diferentes al genérico?
## Método de envío {#sec-s4-dispatch}
\index{S4!method dispatch} \index{method dispatch!S4}
El envío de S4 es complicado porque S4 tiene dos características importantes:
- Herencia múltiple, es decir, una clase puede tener múltiples padres,
- Envío múltiple, es decir, un genérico puede usar múltiples argumentos para elegir un método.
Estas características hacen que S4 sea muy potente, pero también pueden dificultar la comprensión de qué método se seleccionará para una determinada combinación de entradas. En la práctica, mantenga el envío de métodos lo más simple posible evitando la herencia múltiple y reservando el envío múltiple solo cuando sea absolutamente necesario.
Pero es importante describir los detalles completos, por lo que aquí comenzaremos de manera simple con herencia única y despacho único, y avanzaremos hasta los casos más complicados. Para ilustrar las ideas sin atascarse en los detalles, usaremos un **gráfico de clase** imaginario basado en emoji:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/emoji.png")
```
Emoji nos da nombres de clase muy compactos que evocan las relaciones entre las clases. Debería ser sencillo recordar que `r emoji("stuck_out_tongue_winking_eye")` hereda de `r emoji("wink")` que hereda de `r emoji("no_mouth")`, y que `r emoji("sunglasses")` hereda tanto de `r emoji("dark_sunglasses")` como de `r emoji("slightly_smiling_face")`.
### Envío único
\index{S4!single dispatch}
Comencemos con el caso más simple: una función genérica que se distribuye en una sola clase con un solo padre. El método de envío aquí es simple, por lo que es un buen lugar para definir las convenciones gráficas que usaremos para los casos más complejos.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/single.png")
```
Hay dos partes en este diagrama:
- La parte superior, `f(...)`, define el alcance del diagrama. Aquí tenemos un genérico con un argumento, que tiene una jerarquía de clases de tres niveles de profundidad.
- La parte inferior es el **gráfico de métodos** y muestra todos los métodos posibles que podrían definirse. Los métodos que existen, es decir, que se han definido con `setMethod()`, tienen un fondo gris.
Para encontrar el método que se llama, comience con la clase más específica de los argumentos reales, luego siga las flechas hasta que encuentre un método que exista. Por ejemplo, si llamaste a la función con un objeto de la clase `r emoji("wink")`, seguirías la flecha hacia la derecha para encontrar el método definido para la clase más general `r emoji("no_mouth")`. Si no se encuentra ningún método, el envío del método ha fallado y se genera un error. En la práctica, esto significa que siempre debe definir métodos definidos para los nodos terminales, es decir, los del extremo derecho.
\index{S4!pseudo-classes} \index{ANY} Hay dos **pseudoclases** para las que puede definir métodos. Estas se denominan pseudoclases porque en realidad no existen, pero le permiten definir comportamientos útiles. La primera pseudoclase es `ANY` que coincide con cualquier clase[^s4-3]. Por razones técnicas que veremos más adelante, el enlace al método `ANY` es más largo que los enlaces entre las otras clases:
[^s4-3]: La pseudoclase `ANY` de S4 desempeña el mismo papel que la pseudoclase `default` de S3.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/single-any.png")
```
\index{MISSING}
La segunda pseudoclase es `MISSING`. Si define un método para esta pseudoclase, coincidirá siempre que falte el argumento. No es útil para envío único, pero es importante para funciones como `+` y `-` que usan envío doble y se comportan de manera diferente dependiendo de si tienen uno o dos argumentos.
### Herencia múltiple
\index{S4!multiple inheritance} \index{multiple inheritance}
Las cosas se complican más cuando la clase tiene varios padres.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/multiple.png")
```
El proceso básico sigue siendo el mismo: comienza desde la clase real suministrada al genérico, luego sigue las flechas hasta encontrar un método definido. El problema es que ahora hay varias flechas a seguir, por lo que es posible que encuentre varios métodos. Si eso sucede, elige el método más cercano, es decir, requiere viajar con la menor cantidad de flechas.
NB: Si bien el gráfico de métodos es una poderosa metáfora para comprender el envío de métodos, implementarlo de esta manera sería bastante ineficiente, por lo que el enfoque real que usa S4 es algo diferente. Puede leer los detalles en `?Methods_Details`.
¿Qué sucede si los métodos están a la misma distancia? Por ejemplo, imagina que hemos definido métodos para `r emoji("dark_sunglasses")` y `r emoji("slightly_smiling_face")`, y llamamos al genérico `r emoji("sunglasses")`. Tenga en cuenta que no se puede encontrar ningún método para la clase `r emoji ("no_mouth")`, que resaltaré con un doble contorno rojo.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/multiple-ambig.png")
```
Esto se llama método **ambiguo**, y en los diagramas lo ilustraré con un borde de puntos gruesos. Cuando esto sucede en R, recibirá una advertencia y se elegirá el método para la clase que aparece antes en el alfabeto (esto es efectivamente aleatorio y no se debe confiar en él). Cuando descubra una ambigüedad, siempre debe resolverla proporcionando un método más preciso:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/multiple-ambig-2.png")
```
El método alternativo `ANY` aún existe, pero las reglas son un poco más complejas. Como lo indican las líneas punteadas onduladas, el método `ANY` siempre se considera más lejano que un método para una clase real. Esto significa que nunca contribuirá a la ambigüedad.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/multiple-any.png")
```
Con herencias múltiples, es difícil evitar simultáneamente la ambigüedad, asegurarse de que cada método de terminal tenga una implementación y minimizar la cantidad de métodos definidos (para beneficiarse de OOP). Por ejemplo, de las seis formas de definir solo dos métodos para esta llamada, solo una está libre de problemas. Por esta razón, recomiendo utilizar la herencia múltiple con sumo cuidado: deberá pensar detenidamente en el gráfico del método y planificar en consecuencia.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/multiple-all.png")
```
### Envío múltiple
\index{S4!multiple dispatch} \index{multiple dispatch}
Una vez que comprenda la herencia múltiple, comprender el envío múltiple es sencillo. Sigue varias flechas de la misma manera que antes, pero ahora cada método se especifica mediante dos clases (separadas por una coma).
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/single-single.png")
```
No voy a mostrar ejemplos de despacho en más de dos argumentos, pero puede seguir los principios básicos para generar sus propios gráficos de métodos.
La principal diferencia entre la herencia múltiple y el envío múltiple es que hay muchas más flechas a seguir. El siguiente diagrama muestra cuatro métodos definidos que producen dos casos ambiguos:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/single-single-ambig.png")
```
El envío múltiple tiende a ser menos complicado para trabajar que la herencia múltiple porque generalmente hay menos combinaciones de clases de terminales. En este ejemplo, solo hay uno. Eso significa que, como mínimo, puede definir un solo método y tener un comportamiento predeterminado para todas las entradas.
### Despacho múltiple y herencia múltiple
Por supuesto, puede combinar envío múltiple con herencia múltiple:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/single-multiple.png")
```
Un caso aún más complicado se despacha en dos clases, las cuales tienen herencia múltiple:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/s4/multiple-multiple.png")
```
A medida que el gráfico del método se vuelve más y más complicado, se vuelve más y más difícil predecir qué método se llamará dada una combinación de entradas, y se vuelve más y más difícil asegurarse de que no se ha introducido ambigüedad. Si tiene que dibujar diagramas para averiguar qué método se llamará realmente, es una fuerte indicación de que debe volver atrás y simplificar su diseño.
### Ejercicios
1. Dibuje el gráfico del método para `r paste0(code("f("), emoji("sweat_smile"), ", ", emoji("kissing_cat"), code(")"))`.
2. Dibuje el gráfico del método para `r paste0(code("f("), emoji("smiley"), ", ", emoji("wink"), ", ", emoji("kissing_smiling_eyes"), code(")"))`.
3. Tome el último ejemplo que muestra envío múltiple sobre dos clases que usan herencia múltiple. ¿Qué sucede si define un método para todas las clases de terminal? ¿Por qué el método de envío no nos ahorra mucho trabajo aquí?
## S4 y S3 {#sec-s4-s3}
\index{S4!working with S3} \index{S3!working with S4}
Al escribir código S4, a menudo necesitará interactuar con clases y genéricos existentes de S3. Esta sección describe cómo las clases, los métodos y los genéricos de S4 interactúan con el código existente.
### Clases
\index{setOldClass()}
En `slots` y `contains` puede usar clases S4, clases S3 o la clase implícita (@sec-implicit-class) de un tipo base. Para usar una clase S3, primero debe registrarla con `setOldClass()`. Llame a esta función una vez para cada clase de S3, dándole el atributo de clase. Por ejemplo, la base R ya proporciona las siguientes definiciones:
```{r, eval = FALSE}
setOldClass("data.frame")
setOldClass(c("ordered", "factor"))
setOldClass(c("glm", "lm"))
```
Sin embargo, generalmente es mejor ser más específico y proporcionar una definición completa de S4 con `slots` y un `prototype`:
```{r, eval = FALSE}
setClass("factor",
contains = "integer",
slots = c(
levels = "character"
),
prototype = structure(
integer(),
levels = character()
)
)
setOldClass("factor", S4Class = "factor")
```
Por lo general, estas definiciones las debe proporcionar el creador de la clase S3. Si intenta crear una clase S4 sobre una clase S3 proporcionada por un paquete, debe solicitar que el mantenedor del paquete agregue esta llamada a su paquete, en lugar de agregarla a su propio código.
Si un objeto S4 hereda de una clase S3 o un tipo base, tendrá una ranura virtual especial llamada `.Data`. Esto contiene el tipo base subyacente o el objeto S3: \index{.Data}
```{r}
RangedNumeric <- setClass(
"RangedNumeric",
contains = "numeric",
slots = c(min = "numeric", max = "numeric"),
prototype = structure(numeric(), min = NA_real_, max = NA_real_)
)
rn <- RangedNumeric(1:10, min = 1, max = 10)
rn@min
```
Es posible definir métodos de S3 para genéricos de S4 y métodos de S4 para genéricos de S3 (siempre que haya llamado a `setOldClass()`). Sin embargo, es más complicado de lo que parece a primera vista, así que asegúrese de leer detenidamente `?Methods_for_S3`.
### Genéricos
\index{setGeneric()}
Además de crear un nuevo genérico desde cero, también es posible convertir un genérico S3 existente en un genérico S4:
```{r}
setGeneric("mean")
```
En este caso, la función existente se convierte en el método predeterminado (`ANY`):
```{r}
selectMethod("mean", "ANY")
```
NB: `setMethod()` llamará automáticamente a `setGeneric()` si el primer argumento aún no es genérico, lo que le permite convertir cualquier función existente en un genérico S4. Está bien convertir un S3 genérico existente a S4, pero debe evitar convertir funciones regulares a genéricos S4 en paquetes porque eso requiere una coordinación cuidadosa si lo hacen varios paquetes.
### Ejercicios
1. ¿Cómo sería una definición completa de `setOldClass()` para un factor ordenado (es decir, agregar `slots` y `prototype` de la definición anterior)?
2. Defina un método `length` para la clase `Person`.