8.5 Custom conditions
8.5.1 Motivation
The base::log()
function provides a minimal error message.
log(letters)
#> Error in log(letters): non-numeric argument to mathematical function
log(1:10, base = letters)
#> Error in log(1:10, base = letters): non-numeric argument to mathematical function
One could make a more informative error message about which argument is problematic.
my_log <- function(x, base = exp(1)) {
if (!is.numeric(x)) {
rlang::abort(paste0(
"`x` must be a numeric vector; not ", typeof(x), "."
))
}
if (!is.numeric(base)) {
rlang::abort(paste0(
"`base` must be a numeric vector; not ", typeof(base), "."
))
}
base::log(x, base = base)
}
Consider the difference:
8.5.2 Signalling
Create a helper function to describe errors:
abort_bad_argument <- function(arg, must, not = NULL) {
msg <- glue::glue("`{arg}` must {must}")
if (!is.null(not)) {
not <- typeof(not)
msg <- glue::glue("{msg}; not {not}.")
}
rlang::abort(
"error_bad_argument", # <- this is the (error) class, I believe
message = msg,
arg = arg,
must = must,
not = not
)
}
Rewrite the log function to use this helper function:
my_log <- function(x, base = exp(1)) {
if (!is.numeric(x)) {
abort_bad_argument("x", must = "be numeric", not = x)
}
if (!is.numeric(base)) {
abort_bad_argument("base", must = "be numeric", not = base)
}
base::log(x, base = base)
}
See the result for the end user:
8.5.3 Handling
Use class of condition object to allow for different handling of different types of errors
tryCatch(
error_bad_argument = function(cnd) "bad_argument",
error = function(cnd) "other error",
my_log("a")
)
#> [1] "bad_argument"
But note that the first handler that matches any of the signal’s class, potentially in a vector of signal classes, will get control. So put the most specific handlers first.