R y programación orientada a objetos

80

La programación orientada a objetos de una forma u otra es muy posible en R. Sin embargo, a diferencia de, por ejemplo, Python, hay muchas formas de lograr la orientación a objetos:

Mi pregunta es:

¿Qué diferencias principales distinguen estas formas de programación OO en R?

Idealmente, las respuestas aquí servirán como referencia para los programadores de R que intentan decidir qué métodos de programación OO se adaptan mejor a sus necesidades.

Como tal, solicito detalles, presentados de manera objetiva, basados ​​en la experiencia y respaldados por hechos y referencias. Puntos de bonificación por aclarar cómo estos métodos se corresponden con las prácticas estándar de OO.

Paul Hiemstra
fuente
1
Información sobre clases de referencia: stackoverflow.com/questions/5137199/…
Ari B. Friedman
Gracias, ¿podrías volver a publicar el enlace como respuesta? Sería bueno si pudiera incluir un pequeño resumen de qué son las clases de referencia y por qué son preferibles en relación con las clases S3 / S4.
Paul Hiemstra
Un pajarito me susurró al oído que John Chambers publicaría un libro sobre esto. Pero no le digas a nadie que dije eso ... ;-)
Dirk Eddelbuettel
1
¿Podría ese mismo pajarito pegar una respuesta a continuación con más información sobre las clases de Refenence;)
Paul Hiemstra

Respuestas:

34

Clases S3

  • No son realmente objetos, más bien una convención de nomenclatura.
  • Basado en el. sintaxis: por ejemplo, para impresión, printllamadas print.lm print.anova, etc. Y si no se encuentra,print.default

Clases S4

Clases de referencia

proto

  • ggplot2 se escribió originalmente en proto, pero eventualmente se reescribirá usando S3.
  • Concepto ordenado (prototipos, no clases), pero parece complicado en la práctica
  • La próxima versión de ggplot2 parece alejarse de ella
  • Descripción del concepto e implementación

Clases R6

  • Por referencia
  • No depende de las clases S4
  • "La creación de una clase R6 es similar a la clase de referencia, excepto que no es necesario separar los campos y los métodos, y no se pueden especificar los tipos de campos".
Ari B. Friedman
fuente
1
Siéntase libre de editar si tiene otras diferencias para agregar. No voy a llorar si se convierte en CW :-)
Ari B. Friedman
3
no lo olvideslibrary("fortunes"); fortune("strait")
Ben Bolker
1
Una discusión sobre las clases de S4 aquí: stackoverflow.com/questions/3602154/… . La sensación general parece ser que son más problemáticos que ventajas.
Paul Hiemstra
Curiosamente, las nuevas clases R6 reconocen implícitamente que las clases de referencia eran R5 al evitar usar ese número. Que la controversia comience (de nuevo).
Ari B. Friedman
1
El nombre R5 fue utilizado originalmente como una broma por personas distintas a los desarrolladores de las clases de referencia. El nombre R6 es un reconocimiento de "R5", pero no significa que el nombre R5 tuviera ningún respaldo oficial.
mes
19

Editar el 8/3/12: la respuesta a continuación responde a una parte de la pregunta publicada originalmente que desde entonces se ha eliminado. Lo he copiado a continuación, para proporcionar contexto a mi respuesta:

¿Cómo se asignan los diferentes métodos OO a los métodos OO más estándar utilizados, por ejemplo, en Java o Python?


Mi contribución se relaciona con su segunda pregunta, acerca de cómo los métodos OO de R se asignan a métodos OO más estándar. Mientras pensaba en esto en el pasado, volví una y otra vez a dos pasajes, uno de Friedrich Leisch y el otro de John Chambers. Ambos hacen un buen trabajo al articular por qué la programación tipo OO en R tiene un sabor diferente al de muchos otros lenguajes.

Primero, Friedrich Leisch, de "Creación de paquetes R: un tutorial" ( advertencia: PDF ):

S es raro porque es interactivo y tiene un sistema de orientación a objetos. El diseño de clases claramente es programación, pero para que S sea útil como un entorno de análisis de datos interactivo, tiene sentido que sea un lenguaje funcional. En los lenguajes de programación orientada a objetos (POO) "reales" como C ++ o Java, las definiciones de clases y métodos están estrechamente unidas, los métodos son parte de las clases (y por lo tanto de los objetos). Queremos adiciones incrementales e interactivas como métodos definidos por el usuario para clases predefinidas. Estas adiciones se pueden realizar en cualquier momento, incluso sobre la marcha en la línea de comandos mientras analizamos un conjunto de datos. S intenta hacer un compromiso entre la orientación a objetos y el uso interactivo, y aunque los compromisos nunca son óptimos con respecto a todos los objetivos que intentan alcanzar, a menudo funcionan sorprendentemente bien en la práctica.

El otro pasaje proviene del magnífico libro de John Chambers "Software para análisis de datos" . ( Enlace al pasaje citado ):

El modelo de programación OOP difiere del lenguaje S en todo menos en el primer punto, aunque S y algunos otros lenguajes funcionales admiten clases y métodos. Las definiciones de método en un sistema OOP son locales para la clase; No hay ningún requisito de que el mismo nombre para un método signifique lo mismo para una clase no relacionada. Por el contrario, las definiciones de método en R no residen en una definición de clase; conceptualmente, están asociados con la función genérica. Las definiciones de clase entran en la determinación de la selección del método, directamente o por herencia. Los programadores acostumbrados al modelo OOP a veces se sienten frustrados o confundidos porque su programación no se transfiere directamente a R, pero no puede. El uso funcional de los métodos es más complicado, pero también más adaptado a tener funciones significativas, y no se puede reducir a la versión OOP.

Josh O'Brien
fuente
14

S3 y S4 parecen ser los enfoques oficiales (es decir, integrados) para la programación OO. Comencé a usar una combinación de S3 con funciones integradas en la función / método del constructor. Mi objetivo era tener una sintaxis de tipo object $ method () para tener campos semiprivados. Digo semiprivado porque no hay forma de ocultarlos realmente (hasta donde yo sé). Aquí hay un ejemplo simple que en realidad no hace nada:

#' Constructor
EmailClass <- function(name, email) {
    nc = list(
        name = name,
        email = email,
        get = function(x) nc[[x]],
        set = function(x, value) nc[[x]] <<- value,
        props = list(),
        history = list(),
        getHistory = function() return(nc$history),
        getNumMessagesSent = function() return(length(nc$history))
    )
    #Add a few more methods
    nc$sendMail = function(to) {
        cat(paste("Sending mail to", to, 'from', nc$email))
        h <- nc$history
        h[[(length(h)+1)]] <- list(to=to, timestamp=Sys.time())
        assign('history', h, envir=nc)
    }
    nc$addProp = function(name, value) {
        p <- nc$props
        p[[name]] <- value
        assign('props', p, envir=nc)
    }
    nc <- list2env(nc)
    class(nc) <- "EmailClass"
    return(nc)
}

#' Define S3 generic method for the print function.
print.EmailClass <- function(x) {
    if(class(x) != "EmailClass") stop();
    cat(paste(x$get("name"), "'s email address is ", x$get("email"), sep=''))
}

Y un código de prueba:

    test <- EmailClass(name="Jason", "[email protected]")
    test$addProp('hello', 'world')
    test$props
    test
    class(test)
    str(test)
    test$get("name")
    test$get("email")
    test$set("name", "Heather")
    test$get("name")
    test
    test$sendMail("[email protected]")
    test$getHistory()
    test$sendMail("[email protected]")
    test$getNumMessagesSent()

    test2 <- EmailClass("Nobody", "[email protected]")
    test2
    test2$props
    test2$getHistory()
    test2$sendMail('[email protected]')

Aquí hay un enlace a una publicación de blog que escribí sobre este enfoque: http://bryer.org/2012/object-oriented-programming-in-r Agradecería comentarios, críticas y sugerencias sobre este enfoque, ya que no estoy convencido. yo mismo si este es el mejor enfoque. Sin embargo, para el problema que estaba tratando de resolver, funcionó muy bien. Específicamente, para el paquete makeR ( http://jbryer.github.com/makeR ) no quería que los usuarios cambiaran los campos de datos directamente porque necesitaba asegurarme de que un archivo XML que representaba el estado de mi objeto se mantuviera sincronizado. Esto funcionó perfectamente siempre que los usuarios se adhieran a las reglas que describo en la documentación.

jbryer
fuente
10
Estás reinventando las clases de referencia "a mano" con el código anterior ... Simplemente hace que las cosas sean un poco más frágiles.
Simon Urbanek
Gracias Simon. No tenía conocimiento de ReferenceClasses hasta después de que publiqué esto.
jbryer