Menu
Coddy logo textTech

Validators and Helpers

Part of the Object Oriented Programming section of Coddy's R journey. Lesson 5 of 57.

The constructor checks types; a validator checks values. By convention validate_money() takes an object, calls stop() with a clear message for each rule it breaks, and returns the object when everything is fine. call. = FALSE keeps the function call out of the message:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
validate_money <- function(x) {
  if (x$amount < 0) stop("amount must not be negative", call. = FALSE)
  if (nchar(x$currency) != 3) stop("currency must have 3 letters", call. = FALSE)
  x
}
m <- validate_money(new_money(-5, "EUR"))

Output:

Error: amount must not be negative
Execution halted

The helper is the function other code calls. It is named after the class, converts its input to the right types, fills in defaults, and passes the result through the constructor and the validator:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
validate_money <- function(x) {
  if (x$amount < 0) stop("amount must not be negative", call. = FALSE)
  x
}
money <- function(amount, currency = "EUR") {
  validate_money(new_money(as.double(amount), toupper(currency)))
}
m <- money("12", "usd")
cat(m$amount, m$currency)

Output:

12 USD

Code that reads user input catches the validator's errors with tryCatch() and shows conditionMessage(e), the text passed to stop():

validate_age <- function(x) {
  if (x$years < 0) stop("age must not be negative", call. = FALSE)
  if (x$years > 150) stop("age is too large", call. = FALSE)
  x
}
age <- function(years) validate_age(structure(list(years = as.double(years)), class = "age"))
for (input in c("30", "-2", "200")) {
  result <- tryCatch(paste("ok", age(input)$years), error = function(e) conditionMessage(e))
  cat(result, "\n")
}

Output:

ok 30 
age must not be negative 
age is too large 

Each of the three functions has one job. new_money() is strict and fast, for code that already has the right types. validate_money() holds the rules about values, which can be slow or many. money() is friendly and is what the rest of the program uses:

new_money()       # types only; builds the object with structure()
validate_money()  # value rules; stop() with a message, else return x
money()           # converts input, then validate_money(new_money(...))
challenge icon

Challenge

Easy

new_score(points, max) in Score.R builds a score object. Complete the other functions in the same file:

  • validate_score(x) stops with points must not be negative, max must be positive or points must not exceed max, checked in that order, and otherwise returns x.
  • score(points, max = 100) converts both arguments with as.double() and returns validate_score(new_score(...)).
  • format.score(x, ...) returns 45/50 (90%), with the percentage rounded to a whole number.

Use call. = FALSE in every stop(). The supplied code reads lines 45 50, prints the formatted score, or error: and the message.

Your code goes in Score.R. main.R holds the supplied input/output code and cannot be edited.

Try it yourself

source("Score.R")

# Supplied input/output code: keep it as it is
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  s <- tryCatch(score(p[1], p[2]), error = function(e) conditionMessage(e))
  if (is.character(s)) cat("error: ", s, "\n", sep = "") else cat(format(s), "\n", sep = "")
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online R compiler