Menu
Coddy logo textTech

Funciones constructoras

Parte de la sección Programación orientada a objetos del Journey de R de Coddy. Lección 3 de 57.

Como R nunca comprueba los campos de un objeto S3, cada objeto de una clase debe construirse mediante una función, su constructor. Por convención, el constructor de bajo nivel se llama new_ seguido del nombre de la clase. Toma cada campo como argumento, comprueba los tipos con stopifnot() y devuelve el objeto:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money(9.5, "EUR")
cat(m$amount, m$currency)

Salida:

9.5 EUR

stopifnot() detiene el programa cuando una de sus condiciones no es TRUE, y el mensaje indica cuál comprobación ha fallado. Un tipo incorrecto se detecta en el momento en que se construye el objeto, no más tarde, cuando alguna otra función lo utiliza:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money("9.5", "EUR")

Salida:

Error in new_money("9.5", "EUR") : is.double(amount) is not TRUE
Calls: new_money -> stopifnot
Execution halted

El constructor es el único lugar que llama a structure() para la clase, por lo que cada objeto tiene los mismos campos en el mismo orden. El código que necesita muchos objetos lo llama en un bucle o con lapply():

new_point <- function(x = 0, y = 0) {
  stopifnot(is.numeric(x), is.numeric(y))
  structure(list(x = x, y = y), class = "point")
}
pts <- lapply(1:3, function(i) new_point(i, i * i))
for (p in pts) cat(p$x, p$y, "\n")

Salida:

1 1 
2 4 
3 9 

Un objeto S3 no tiene que ser una lista. Un constructor puede etiquetar un vector simple y almacenar información adicional como atributos, que es la forma en que funcionan los factores y las fechas propios de R. attr(x, "unit") lee un atributo:

new_distance <- function(value = double(), unit = "m") {
  stopifnot(is.double(value), is.character(unit))
  structure(value, unit = unit, class = "distance")
}
d <- new_distance(c(1.5, 2), "km")
print(attr(d, "unit"))
print(unclass(d)[2] * 1000)

Salida:

[1] "km"
[1] 2000
challenge icon

Desafío

Fácil

Completa el constructor new_book(title, author, pages) en Book.R. Comprueba con stopifnot() que title y author sean de tipo carácter y que pages sea numérico, y devuelve una lista con esos tres campos y la clase book. Después completa book_label(b), que devuelve Dune by Frank Herbert, 412 pages.

El código proporcionado lee líneas con el formato title;author;pages. Pasa pages como un número cuando está compuesto por dígitos y como el texto original en caso contrario, imprime book: y la etiqueta de cada objeto, e imprime rejected: y el título cuando el constructor se detiene.

Tu código va en Book.R. main.R contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

source("Book.R")

# Código de entrada/salida suministrado: déjalo tal como está
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, ";")[[1]]
  pages <- if (grepl("^[0-9]+$", p[3])) as.numeric(p[3]) else p[3]
  b <- tryCatch(new_book(p[1], p[2], pages), error = function(e) NULL)
  if (is.null(b)) {
    cat("rejected: ", p[1], "\n", sep = "")
  } else {
    cat(class(b), ": ", book_label(b), "\n", sep = "")
  }
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de R online