Constructor Functions
Part of the Object Oriented Programming section of Coddy's R journey. Lesson 3 of 57.
Because R never checks an S3 object's fields, every object of a class should be built by one function, its constructor. By convention the low-level constructor is called new_ plus the class name. It takes each field as an argument, checks the types with stopifnot() and returns the object:
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)Output:
9.5 EURstopifnot() stops the program when one of its conditions is not TRUE, and the message names the failing check. A wrong type is caught at the moment the object is built, not later when some other function uses it:
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")Output:
Error in new_money("9.5", "EUR") : is.double(amount) is not TRUE
Calls: new_money -> stopifnot
Execution haltedThe constructor is the only place that calls structure() for the class, so every object has the same fields in the same order. Code that needs many objects calls it in a loop or with 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")Output:
1 1
2 4
3 9 An S3 object does not have to be a list. A constructor can label a plain vector and store extra facts as attributes, which is how R's own factors and dates work. attr(x, "unit") reads an attribute:
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)Output:
[1] "km"
[1] 2000Challenge
EasyComplete the constructor new_book(title, author, pages) in Book.R. It checks with stopifnot() that title and author are character and pages is numeric, and returns a list with those three fields and the class book. Then complete book_label(b), which returns Dune by Frank Herbert, 412 pages.
The supplied code reads lines title;author;pages. It passes pages as a number when it is made of digits and as the original text otherwise, prints book: and the label for each object, and prints rejected: and the title when the constructor stops.
Your code goes in Book.R. main.R holds the supplied input/output code and cannot be edited.
Try it yourself
source("Book.R")
# Supplied input/output code: keep it as it is
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 = "")
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1S3 Basics
Working With FilesLists With a ClassConstructor FunctionsPrint and Format MethodsValidators and HelpersRecap - Temperatures4Encapsulation in R
Closures as ObjectsEnvironments as ObjectsAccessor FunctionsGuarding StateRecap - Parking Meter2S3 Generics and Methods
Generics and UseMethodDefault MethodsMethods for Base GenericsNextMethod BasicsRecap - Shape Areas8Reference Classes
Defining Reference ClassesMethods and Field UpdatesCopy SemanticsInheritance and callSuperRecap - Task Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Online R compiler