Introdución

En los cinco capítulos siguientes, aprenderá sobre la programación orientada a objetos (POO). POO es un poco más desafiante en R que en otros lenguajes porque:

En general, en R, la programación funcional es mucho más importante que la programación orientada a objetos, porque normalmente resuelve problemas complejos descomponiéndolos en funciones simples, no en objetos simples. Sin embargo, hay razones importantes para aprender cada uno de los tres sistemas:

El objetivo de este breve capítulo introductorio es brindarle un vocabulario importante y algunas herramientas para identificar los sistemas de POO en la naturaleza. Luego, los siguientes capítulos se sumergen en los detalles de los sistemas de POO de R:

  1. 12  Tipos básicos detalla los tipos básicos que forman la base subyacente a todos los demás sistemas OO.

  2. 13  S3 presenta S3, el sistema OO más simple y más utilizado.

  3. 14  R6 analiza R6, un sistema OO encapsulado creado sobre entornos.

  4. 15  S4 introduce S4, que es similar a S3 pero más formal y más estricto.

  5. 16  Compensaciones compara estos tres sistemas OO principales. Al comprender las ventajas y desventajas de cada sistema, puede apreciar cuándo usar uno u otro.

Este libro se centra en la mecánica de la programación orientada a objetos, no en su uso efectivo, y puede ser un desafío comprenderlo completamente si no ha realizado antes programación orientada a objetos. Quizás se pregunte por qué elegí no proporcionar una cobertura útil más inmediata. Me he centrado en la mecánica aquí porque necesitan estar bien descritas en alguna parte (escribir estos capítulos requirió una cantidad considerable de lectura, exploración y síntesis de mi parte), y usar OOP de manera efectiva es lo suficientemente complejo como para requerir un tratamiento del tamaño de un libro; simplemente no hay suficiente espacio en R Avanzado para cubrirlo con la profundidad requerida.

Sistemas de POO

Diferentes personas usan los términos de programación orientada a objetos de diferentes maneras, por lo que esta sección proporciona una descripción general rápida del vocabulario importante. Las explicaciones están necesariamente comprimidas, pero volveremos a estas ideas varias veces.

La razón principal para usar POO es polimorfismo (literalmente: muchas formas). El polimorfismo significa que un desarrollador puede considerar la interfaz de una función por separado de su implementación, lo que hace posible usar la misma forma de función para diferentes tipos de entrada. Esto está estrechamente relacionado con la idea de encapsulación: el usuario no necesita preocuparse por los detalles de un objeto porque están encapsulados detrás de una interfaz estándar.

Para ser concretos, el polimorfismo es lo que permite que summary() produzca diferentes salidas para variables numéricas y factoriales:

diamonds <- ggplot2::diamonds

summary(diamonds$carat)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    0.20    0.40    0.70    0.80    1.04    5.01

summary(diamonds$cut)
#>      Fair      Good Very Good   Premium     Ideal 
#>      1610      4906     12082     13791     21551

Podrías imaginar summary() que contiene una serie de declaraciones if-else, pero eso significaría que solo el autor original podría agregar nuevas implementaciones. Un sistema OOP hace posible que cualquier desarrollador amplíe la interfaz con implementaciones para nuevos tipos de entrada.

Para ser más precisos, los sistemas OO llaman al tipo de un objeto su clase, y una implementación para una clase específica se llama método. En términos generales, una clase define lo que un objeto es y los métodos describen lo que ese objeto puede hacer. La clase define los campos, los datos que posee cada instancia de esa clase. Las clases están organizadas en una jerarquía de modo que si no existe un método para una clase, se usa el método de su padre y se dice que el hijo hereda el comportamiento. Por ejemplo, en R, un factor ordenado hereda de un factor regular y un modelo lineal generalizado hereda de un modelo lineal. El proceso de encontrar el método correcto dada una clase se llama despacho de métodos.

Hay dos paradigmas principales de programación orientada a objetos que difieren en cómo se relacionan los métodos y las clases. En este libro, tomaremos prestada la terminología de Extending R (Chambers 2016) y llamaremos a estos paradigmas encapsulados y funcionales:

  • En la programación orientada a objetos encapsulada, los métodos pertenecen a objetos o clases, y las llamadas a métodos normalmente se ven como object.method(arg1, arg2). Esto se denomina encapsulado porque el objeto encapsula tanto los datos (con campos) como el comportamiento (con métodos), y es el paradigma que se encuentra en los lenguajes más populares.

  • En la programación orientada a objetos funcional, los métodos pertenecen a funciones genéricas y las llamadas a métodos se parecen a las llamadas a funciones ordinarias: generic(object, arg2, arg3). Esto se llama funcional porque desde el exterior parece una llamada de función regular, e internamente los componentes también son funciones.

Con esta terminología en la mano, ahora podemos hablar precisamente de los diferentes sistemas OO disponibles en R.

POO en R

Base R proporciona tres sistemas OOP: S3, S4 y clases de referencia (RC):

  • S3 es el primer sistema de POO de R y se describe en Modelos estadísticos en S (Chambers y Hastie 1992). S3 es una implementación informal de POO funcional y se basa en convenciones comunes en lugar de garantías inquebrantables. Esto hace que sea fácil comenzar, proporcionando una forma económica de resolver muchos problemas simples.

  • S4 es una reescritura formal y rigurosa de S3 y se introdujo en Programación con datos (Chambers 1998). Requiere más trabajo inicial que S3, pero a cambio ofrece más garantías y una mayor encapsulación. S4 se implementa en el paquete base de métodos, que siempre se instala con R.

    (Quizás se pregunte si existen S1 y S2. No lo hacen: S3 y S4 fueron nombrados de acuerdo con las versiones de S que acompañaban. Las dos primeras versiones de S no tenían ningún marco de POO.)

  • RC implementa OO encapsulado. Los objetos RC son un tipo especial de objetos S4 que también son mutables, es decir, en lugar de usar la semántica habitual de copiar al modificar de R, se pueden modificar en su lugar. Esto los hace más difíciles de razonar, pero les permite resolver problemas que son difíciles de resolver en el estilo OOP funcional de S3 y S4.

Los paquetes CRAN proporcionan otros sistemas de POO:

  • R6 (Chang 2017) implementa OOP encapsulado como RC, pero resuelve algunos problemas importantes. En este libro, aprenderá sobre R6 en lugar de RC, por las razones descritas en la 14.5 ¿Por qué R6?.

  • R.oo (Bengtsson 2003) proporciona algo de formalismo además de S3 y hace posible tener objetos mutables de S3.

  • proto (Grothendieck, Kates, y Petzoldt 2016) implementa otro estilo de programación orientada a objetos basado en la idea de prototipos, que desdibujan las distinciones entre clases e instancias de clases (objetos). Me enamoré brevemente de la programación basada en prototipos (Wickham 2011) y la usé en ggplot2, pero ahora creo que es mejor seguir con los formularios estándar.

Aparte del R6, que es ampliamente utilizado, estos sistemas son principalmente de interés teórico. Tienen sus puntos fuertes, pero pocos usuarios de R los conocen y los entienden, por lo que es difícil que otros los lean y contribuyan a su código.

sloop

Antes de continuar, quiero presentar el paquete sloop:

library(sloop)

El paquete sloop (piense en “navegar los mares de OOP”) proporciona una serie de ayudantes que completan las piezas que faltan en la base R. El primero de ellos es sloop::otype(). Hace que sea fácil descifrar el sistema OOP utilizado por un objeto capturado de forma salvaje:

otype(1:10)
#> [1] "base"

otype(mtcars)
#> [1] "S3"

mle_obj <- stats4::mle(function(x = 1) (x - 2) ^ 2)
otype(mle_obj)
#> [1] "S4"

Utilice esta función para averiguar qué capítulo leer para entender cómo trabajar con un objeto existente.


  1. La excepción es Julia, que también usa la función genérica de POO. En comparación con R, la implementación de Julia está completamente desarrollada y tiene un rendimiento extremo.↩︎