Herencia en R

Con respecto a R, ¿Puede alguien explicarme, con respecto a la herencia de objetos, si tengo S4 objeto X, que contiene Y, si Y tiene un inicializador, cómo puede llamarse ese inicializador desde el inicializador de X, cuando X es construido.

Un primer pase, no lo suficientemente bueno

Aquí hay dos clases

 .A <- setClass("A", representation(a="integer")) .B <- setClass("B", contains="A", representation(b="integer")) 

El símbolo .A es una función generadora de clases (esencialmente una llamada a new() ), y es una adición relativamente nueva al paquete de métodos.

Aquí escribimos un método de inicialización, A, utilizando callNextMethod para llamar al siguiente método (el constructor predeterminado, inicializar, CUALQUIER método) para la clase

 setMethod("initialize", "A", function(.Object, ..., a=integer()) { ## do work of initialization cat("A\n") callNextMethod(.Object, ..., a=a) }) 

El argumento correspondiente a la ranura a=a viene después ... para que la función no asigne ningún argumento sin nombre a a ; esto es importante porque se supone que los argumentos sin nombre (desde ?initialize ) se utilizan para inicializar las clases base, no los espacios; la importancia de esto se vuelve evidente a continuación. Del mismo modo para "B":

 setMethod("initialize", "B", function(.Object, ..., b=integer()) { cat("B\n") callNextMethod(.Object, ..., b=b) }) 

y en acción

 > b <- .B(a=1:5, b=5:1) B A > b An object of class "B" Slot "b": [1] 5 4 3 2 1 Slot "a": [1] 1 2 3 4 5 

En realidad, esto no es del todo correcto, porque la initialize predeterminada es un constructor de copia

 .C <- setClass("C", representation(c1="numeric", c2="numeric")) c <- .C(c1=1:5, c2=5:1) > initialize(c, c1=5:1) An object of class "C" Slot "c1": [1] 5 4 3 2 1 Slot "c2": [1] 5 4 3 2 1 

y nuestro método de inicialización ha roto este aspecto del contrato

 > initialize(b, a=1:5) # BAD: no copy construction B A An object of class "B" Slot "b": integer(0) Slot "a": [1] 1 2 3 4 5 

La copia de la construcción resulta bastante útil, por lo que no queremos romperla.

Retención de construcción de copia

Hay dos soluciones empleadas para conservar la funcionalidad de construcción de copias. El primero evita definir un método de inicialización, pero en su lugar crea una vieja función simple como un constructor

 .A1 <- setClass("A1", representation(a="integer")) .B1 <- setClass("B1", contains="A1", representation(b="integer")) A1 <- function(a = integer(), ...) { .A1(a=a, ...) } B1 <- function(a=integer(), b=integer(), ...) { .B1(A1(a), b=b, ...) } 

Estas funciones incluyen ... como argumentos, para que la clase "B1" se pueda extender y su constructor aún se use. Esto es realmente bastante atractivo; el constructor puede tener una firma sensible con argumentos documentados. initialize puede usarse como un constructor de copia (recuerde, no hay initialize, A1-method o initialize, B1-method, entonces la llamada .A1() invoca el método predeterminado, copy-constructor capaz de inicializar). La función ( .B1(A1(a), b=b, ...) dice "llame al generador para la clase B1, con un argumento sin nombre creando su superclase usando el constructor" A1 ", y un argumento con nombre correspondiente a la ranura b "Como se mencionó anteriormente, desde ?initialize , los argumentos sin nombre (s) se usan para inicializar superclases (con clases plurales cuando la estructura de clases involucra herencia múltiple). El uso de constructores significa que las clases A1 y B1 pueden ser ignorantes. de la estructura e implementación de cada uno.

La segunda solución, utilizada con menos frecuencia en toda su gloria, es escribir un método de inicialización que conserve la construcción de copias, siguiendo las líneas de

 .A2 <- setClass("A2", representation(a1="integer", a2="integer"), prototype=prototype(a1=1:5, a2=5:1)) setMethod("initialize", "A2", function(.Object, ..., a1=.Object@a1, a2=.Object@a2) { callNextMethod(.Object, ..., a1=a1, a2=a2) }) 

El argumento a1=.Object@a1 usa el valor actual de la ranura a1 de .Object como valor predeterminado, relevante cuando el método se usa como un constructor de copia. El ejemplo ilustra el uso de un prototype para proporcionar valores iniciales diferentes de los vectores de longitud 0. En acción:

 > a <- .A2(a2=1:3) > a An object of class "A1" Slot "a1": [1] 1 2 3 4 5 Slot "a2": [1] 1 2 3 > initialize(a, a1=-(1:3)) # GOOD: copy constructor An object of class "A1" Slot "a1": [1] -1 -2 -3 Slot "a2": [1] 1 2 3 

Lamentablemente, este enfoque falla al tratar de inicializar una clase derivada de una clase base.

Otras Consideraciones

Un último punto es la estructura del método de inicialización en sí mismo. Ilustrado arriba es el patrón

 ## do class initialization steps, then... callNextMethod(<...>) 

entonces callNextMethod() está al final del método de inicialización. Una alternativa es

 .Object <- callNextMethod(<...>) ## do class initialization steps by modifying .Object, eg,... .Object@a <- <...> .Object 

La razón para preferir el primer enfoque es que hay menos copia involucrada; la inicialización predeterminada, CUALQUIER método llena las ranuras con un mínimo de copia, mientras que el método de actualización de ranura copia el objeto completo cada vez que se modifica una ranura; esto puede ser muy malo si el objeto contiene vectores grandes.