Site icon R-bloggers

Make error messages your own

[This article was first published on Code R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

The stop() function allows you to terminate the execution of a function if there is a fatal problem.

For example, imagine this code that calculates the square root of a number but only if the input number is positive.

real_root <- function(x) {
   if (x < 0) {
      stop("'x' cannot be negative.")   
   } 
   sqrt(x)
}
real_root(2)
## [1] 1.414214
real_root(-2)
## Error in real_root(-2): 'x' cannot be negative.

If x is negative, the function throws an error. Now let’s imagine that this function is part of a package and that the author wants all error messages to always be in upper case. And instead of making sure that everything is capitalised, they prefer to use the toupper() function:

toupper("lowercase")
## [1] "LOWERCASE"

Then they decide to write the STOP() function, which is a loud version of stop():

STOP <- function(message) {
   stop(toupper(message))
}

And now they create REAL_ROOT() that uses that loud stop.

REAL_ROOT <- function(x) {
   if (x < 0) {
      STOP("'x' cannot be negative.")   
   } 
   sqrt(x)
}
REAL_ROOT(2)
## [1] 1.414214
REAL_ROOT(-2)
## Error in STOP("'x' cannot be negative."): 'X' CANNOT BE NEGATIVE.

It seems to work fine, but there is a slight issue. Before, the error was coming from inside real_root(), so the error message gave the user a clue as to where the problem layed (Error in real_root(-2) :...). With this new setup, since the error technically occurs inside STOP(), the error message becomes useless. Worse, it confuses the user!

One solution is to directly hide the call:

STOP <- function(message) {
   stop(toupper(message), call. = FALSE)
}
REAL_ROOT(-2)
## Error: 'X' CANNOT BE NEGATIVE.

Now the message isn’t that useful, but at least it’s not actively confusing

But it would be nice to be able to somehow capture the previous call to be part of the error message. That is, make the STOP() function “know” which other expression called it and use that as text for the error message. And that can be done with the sys.call() function.

The sys.call() function captures the code that called a function, but has an argument that allows you to go “backwards” in the expression tree.

outer_function <- function(x) {
   inner_function(x)
}

inner_function <- function(x) {
   sys.call(x)
}

outer_function() calls inner_function() with the code inner_function(x). Since x is 0, inner_function() executes sys.call(0), which returns the last call, which is inner_function(x).

outer_function(0)
## inner_function(x)

But with the argument x set to -1, inner_function() executes sys.call(-1), which returns the penultimate call: outer_function(-1).

outer_function(-1)
## outer_function(-1)

The other piece is the simpleError() function, which can generate an error object that is then correctly interpreted by stop().

message <- "this is an error"
error <- simpleError(toupper(message))
stop(error)
## Error: THIS IS AN ERROR

Putting the two pieces together, you get:

STOP <- function(message) {
   error <- simpleError(toupper(message), call = sys.call(-1))
   stop(error)
}

And now REAL_ROOT() spits out an error as as useful as it is loud

REAL_ROOT(-2)
## Error in REAL_ROOT(-2): 'X' CANNOT BE NEGATIVE.

To leave a comment for the author, please follow the link and comment on their blog: Code R.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.