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 paquetes sp y distr. 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.