2.3 Programación orientada a objetos (funciones genéricas)
“Everything that exists in R is an object.”
— John M. Chambers
R implementa programación orientada a objetos (OOP).
Por ejemplo, es bien conocido que algunas funciones (entre ellas print()
, plot()
o summary()
) se comportan de manera diferente dependiendo de la clase (el tipo de objeto) de sus argumentos, son las denominadas funciones genéricas.
Realmente R dispone de varios sistemas de OOP, entre ellos podríamos destacar (ver capítulos en Object-oriented programming de Advanced R):
S3: Es un sistema muy simple, las clases no tienen una definición formal (no se verifica su consistencia). Es el empleado en el paquete
base
de R y en la mayoría de paquetes que usan OOP. Descrito inicialmente en:Becker R.A., Chambers J.M. y Wilks A.R. (1988), The New S Language: A Programming Environment for Data Analysis and Graphics (A.K.A. the Blue Book). Chapman & Hall.
Chambers J.M. y Hastie T.J. eds. (1992), Statistical Models in S (A.K.A. the White Book). Chapman & Hall.
S4 (no lo recomiendo): Es similar a S3 pero mucho más formal. Está implementado en el paquete
methods
(uno de los paquetes base) de R. Se emplea por ejemplo en los paquetessp
ydistr
. Descrito inicialmente en:Chambers J.M. (1998), Programming with Data (A.K.A. the Green Book). Springer.
R6: Es un sistema OOP encapsulado similar al de otros lenguajes de programación. Está implementado en el paquete
R6
(no se instala por defecto).
Yo en principio recomendaría usar el sistema S3, aunque es bastante rudimentario y puede resultar inicialmente confuso a programadores con experiencia en otros lenguajes.
En cualquier caso es muy recomendable conocer su funcionamiento.
Este sistema esta basado en funciones genéricas.
La clase es un atributo de los objetos (encapsulación), una cadena de texto o un vector de cadenas (herencia), al que se puede acceder con la función class()
.
A partir de la clase del argumento, la función genérica determina el método (función especializada) al que debe llamar (polimorfismo).
En S3 el despacho de métodos (method dispatch) es muy simple, si la función genérica es generica()
y la clase del primer argumento es "clase"
, se llama a la función (método) generica.clase()
si existe.
Si la clase del objeto es heredada (un vector de cadenas), se van buscando los métodos por orden de parentesco y si no se encuentra ninguno, se llama al método por defecto generica.default()
(se llama a la primera función de paste0("generica.", c(class(x), "default"))
que se encuentre en la ruta de búsqueda; podríamos reemplazarla…).
La función genérica suele ser muy sencilla, básicamente incluye una llamada a UseMethod("generica")
.
Por ejemplo:
plot
## function (x, y, ...)
## UseMethod("plot")
## <bytecode: 0x000000001cb688c0>
## <environment: namespace:base>
Podemos obtener los métodos asociados a una función genérica con methods(genérica)
.
Por ejemplo:
methods(plot)
## [1] plot,ANY-method plot,color-method plot.acf*
## [4] plot.data.frame* plot.decomposed.ts* plot.default
## [7] plot.dendrogram* plot.density* plot.ecdf
## [10] plot.factor* plot.formula* plot.function
## [13] plot.ggplot* plot.gtable* plot.hcl_palettes*
## [16] plot.hclust* plot.histogram* plot.HoltWinters*
## [19] plot.isoreg* plot.lm* plot.medpolish*
## [22] plot.mlm* plot.ppr* plot.prcomp*
## [25] plot.princomp* plot.profile.nls* plot.R6*
## [28] plot.raster* plot.spec* plot.stepfun
## [31] plot.stl* plot.table* plot.trans*
## [34] plot.ts plot.tskernel* plot.TukeyHSD*
## see '?methods' for accessing help and source code
Podemos acceder a la ayuda del correspondiente método de la forma habitual (e.g. ?plot.lm
), pero puede que algunos métodos no sean objetos definidos como exportables en el namespace del paquete que los implementa (los marcados con un *
) y por tanto no son en principio accesibles para el usuario.
Siempre podemos acceder a ellos empleando paquete:::metodo
o getAnywhere(metodo)
(e.g. stats:::plot.lm
o getAnywhere(plot.lm)
).
Para listar los métodos disponibles para una clase, podemos emplear el parámetro class
.
Por ejemplo:
methods(class = "lm")
## [1] add1 alias anova case.names coerce
## [6] confint cooks.distance deviance dfbeta dfbetas
## [11] drop1 dummy.coef effects extractAIC family
## [16] formula fortify hatvalues influence initialize
## [21] kappa labels logLik model.frame model.matrix
## [26] nobs plot predict print proj
## [31] qr residuals rstandard rstudent show
## [36] simulate slotsFromS3 summary variable.names vcov
## see '?methods' for accessing help and source code
Para una programación orientada a objetos más formal la recomendación es emplear el sistema R6.