r s4

¿Es una mala práctica acceder a las ranuras de objetos S4 directamente usando @?



(4)

Esta es casi una pregunta filosófica: ¿es malo acceder y / o establecer ranuras de objetos S4 directamente usando @ ?

Siempre me dijeron que era una mala práctica, y que los usuarios deberían usar los métodos S4 "de acceso", y que los desarrolladores deberían proporcionarles estos. Pero me gustaría saber si alguien sabe el verdadero negocio detrás de esto.

Aquí hay un ejemplo utilizando el paquete sp (pero podría generalizarse para cualquier clase de S4):

> library(sp) > foo <- data.frame(x = runif(5), y = runif(5), bar = runif(5)) > coordinates(foo) <- ~x+y > class(foo) [1] "SpatialPointsDataFrame" attr(,"package") [1] "sp" > str(foo) Formal class ''SpatialPointsDataFrame'' [package "sp"] with 5 slots ..@ data :''data.frame'': 5 obs. of 1 variable: .. ..$ bar: num [1:5] 0.621 0.273 0.446 0.174 0.278 ..@ coords.nrs : int [1:2] 1 2 ..@ coords : num [1:5, 1:2] 0.885 0.763 0.591 0.709 0.925 ... .. ..- attr(*, "dimnames")=List of 2 .. .. ..$ : NULL .. .. ..$ : chr [1:2] "x" "y" ..@ bbox : num [1:2, 1:2] 0.591 0.155 0.925 0.803 .. ..- attr(*, "dimnames")=List of 2 .. .. ..$ : chr [1:2] "x" "y" .. .. ..$ : chr [1:2] "min" "max" ..@ proj4string:Formal class ''CRS'' [package "sp"] with 1 slots .. .. ..@ projargs: chr NA > foo@data bar 1 0.6213783 2 0.2725903 3 0.4458229 4 0.1743419 5 0.2779656 > foo@data <- data.frame(bar = letters[1:5], baz = runif(5)) > foo@data bar baz 1 a 0.22877446 2 b 0.93206667 3 c 0.28169866 4 d 0.08616213 5 e 0.36713750


Como desarrollador de una clase S4, mi opinión es:

Si lee slots con @, lo hace bajo su propio riesgo (como en casi todo lo que hace en R: vea algunos ejemplos famosos a continuación). Dicho esto, las ranuras de una clase S4 son en realidad parte de la interfaz documentada .

Las principales ventajas del acceso a través de @ I see es la velocidad:

> microbenchmark (accessor = wl (chondro), direct = chondro@wavelength) Unit: nanoseconds expr min lq median uq max 1 accessor 333431 341289.5 346784.5 366737.5 654219 2 direct 165 212.5 395.0 520.0 1440

(La función de acceso realiza una verificación valiosa además de devolver la ranura @wavelength que causa la diferencia. Espero que todas las funciones de acceso público decentes aseguren la validez)

Incluso recomiendo usar el acceso de lectura a las ranuras de mi clase en situaciones de tiempo crítico (por ejemplo, si se accede a muchos subconjuntos del mismo objeto, puede valer la pena saltarse la verificación de la validez de un objeto sin cambios cada vez), y en En el código de mi paquete predominantemente leo las ranuras directamente, asegurando la validez al comienzo de las funciones y al final de las funciones donde el objeto podría haber quedado inválido. Se puede argumentar que la decisión de diseño (R) de que @<- no comprueba la validez causa una sobrecarga enorme en la práctica porque los métodos que trabajan en objetos S4 no pueden confiar en que el objeto sea válido y, por lo tanto, incluso los métodos con acceso de solo lectura tienen que hacerlo. Comprobación de validez.

Si piensa en el acceso de escritura a un espacio, debe saber realmente lo que está haciendo. @<- no realiza ninguna comprobación de validez, el acceso oficial de escritura debe hacer eso. Y, el acceso de escritura posiblemente hace mucho más que solo actualizar una ranura para mantener el estado del objeto consistente.

Entonces, si escribes en un espacio, espera encontrarte en el infierno y no te quejes. ;-)

Pensando un poco más a lo largo de la línea filosófica de esto: mi paquete es público bajo GPL. No solo le permito que adapte el código a su necesidad, sino que lo aliento a desarrollar / adaptar el código a sus necesidades. En realidad, es realmente fácil en R: todo está ya en una sesión R interactiva normal, incluido el acceso a las ranuras. Lo que está bastante en línea con las decisiones de diseño que hacen a R muy poderosa pero permiten cosas como

> T <- FALSE > `+` <- `-` > pi <- 3 > pi + 2 [1] 1


En esta pregunta, un -er pregunta por qué no pueden encontrar la ranura end en un objeto de Bioconductor IRanges; después de todo, hay accesores de start() , width() y end() y ranuras de start y width . La respuesta es porque la forma en que los usuarios interactúan con la clase difiere de cómo se implementa. En este caso, la implementación se basa en la simple observación de que no es eficiente en el espacio almacenar tres valores (inicio, final, ancho) cuando solo dos (¡dos de ellos hasta el desarrollador!) Son suficientes. Ejemplos similares pero más profundos de divergencia entre la interfaz y la implementación están presentes en otros objetos S4 y en instancias comunes de S3 como la devuelta por lm , donde los datos almacenados en la clase son apropiados para el cálculo posterior en lugar de adaptarse para representar las cantidades que un determinado el usuario podría estar más interesado. No vendrá nada bueno si tuviera que alcanzar esa instancia lm y cambiar un valor, por ejemplo, el elemento de coefficients . Esta separación entre la interfaz y la implementación le da al desarrollador mucha libertad para brindar una experiencia de usuario razonable y constante, tal vez compartida con otras clases similares, pero para implementar (y cambiar la implementación) clases de manera que la programación tenga sentido.

Supongo que esto no responde realmente a su pregunta, pero el desarrollador no espera que el usuario acceda directamente a las tragamonedas y el usuario no debe esperar que el acceso directo a las ranuras sea una forma adecuada de interactuar con la clase.


En general, es una buena práctica de programación separar el contenido de un objeto de la interfaz, consulte este artículo de wikipedia . La idea es tener la interfaz separada de la implementación, de esa manera la implementación puede cambiar considerablemente, sin afectar el código que interactúa con ese código, por ejemplo, su script. Por lo tanto, el uso de @ crea un código menos robusto que es menos probable que funcione en unos pocos años. Por ejemplo, en el paquete sp mencionado por @mdsummer, la implementación de la forma en que se almacenan los polígonos puede cambiar debido a la velocidad o el conocimiento progresivo. Al usar @ , su código se descompone, al usar la interfaz, su código aún funciona. Excepto por supuesto si la interfaz también cambia. Pero los cambios en la implementación son mucho más probables que los cambios en la interfaz.


En resumen, el desarrollador debe proporcionar métodos para cada caso de uso, pero en la práctica esto es bastante difícil y es complicado cubrir cada uso posible. Técnicamente y en lo que a mí respecta, si necesita más de lo que el desarrollador proporciona y debe usar "@" para obtener características no expuestas, entonces usted es un desarrollador (la distinción es borrosa aquí en el software GNU).

El paquete sp es un buen ejemplo para hacer esta pregunta, ya que las complicaciones de las estructuras de datos jerárquicos requeridas por "polígonos" y "líneas" presentan algunos problemas bastante simples. Aqui hay uno:

El método de las coordinates() para los polígonos y las líneas devuelve solo un centroide para cada objeto, aunque para los puntos devuelve todas las "coordenadas" del objeto, pero eso se debe a que los "puntos" son "uno a uno". Un objeto, una coordenada, verdadero también para SpatialPoints y SpatialPointsDataFrame. Esto no es cierto para Línea y Polígono, o Líneas y Polígonos, o SpatialLines y SpatialPolygons, o SpatialLinesDataFrame y SpatialPolygonsDataFrame. Estos están compuestos inherentemente de> dos pistas de línea de coordenadas o> "anillos" de poli de tres coordenadas. ¿Cómo obtener la coordenada de cada vértice en cada Polígono de cada SpatialPolygon multi-ramificado? No puedes hacerlo a menos que profundices en la estructura del desarrollador con "@".

¿Es negligente que los desarrolladores no hayan proporcionado esto? No, las ventajas superan con creces los problemas que cualquier usuario particular puede ver en retrospectiva. En general, el hecho de que puede profundizar es una ventaja enorme, pero asume automáticamente la responsabilidad del desarrollador, y probablemente complique la situación si decide compartir sus esfuerzos sin envolverlos en métodos.