This R package makes it easy to add a full range and maintainable error handling to your functions and packages, without inflating your code.
Make error handling in R less tedious
# Install development version from GitHub
devtools::install_github('a-maldet/funky', build_opts = NULL)
devtools::install_github('a-maldet/composerr', build_opts = NULL)
library(composerr)
composerr()
: Create new error handling function from scratch.composerr(err_h = err_h_parent)
: Create new (child) error handling function from another (parent) error handlererr_h_parent()
.composerr_halt(err_h)
: Halt error processing for the error handlererr_h
and accumulate all error messages generated by callingerr_h(msg, ...)
in an internal error stack oferr_h
.composerr_flush(err_h)
: Flush entire internal error stack oferr_h
, holding all stacked error messages generated by callingerr_h(msg, ...)
.composerr_counterr(err_h)
: Number of accumulated error messages generated by callingerr_h()
.composerr_get_action(err_h)
: Retrieve the ultimate error handler, which defines the ultimate processing of the full error message, whenerr_h(msg, ...)
is called.validate_composerr(err_h)
: Validate thaterr_h
is indeed an error handler created bycomposerr()
.
The call
err_h1 <- composerr(before = "A problem appeared: ")
creates a composerr
class object err_h1
, which is basically a
function err_h1 = function(msg, action = NULL, ...)
. Calling
err_h1(msg)
will throw an error with a meaningful error message:
err_h1("The fridge is empty.")
# Error: A problem appeared: The fridge is empty.
As one can see, the error message "The fridge is empty."
is
concatenated to the string "A problem appeared: "
, which was defined
in the creation of err_h1
with the argument before
. If the argument
collapse
was set to NULL
, then the total the resulting error message
will be generated as follows:
msg_new <- paste0(before, msg, after)
If the argument collapse
is not set to NULL
, but is a string, then
the strings before
and after
also will be concatenated, but
afterwards the resulting character vector will be collapsed as well:
msg_new <- paste(paste0(before, msg, after), collapse = collapse)
Sometimes it is useful, to create an error handler err_h_child
from
another error handler err_h_parent
. In this case err_h_child
is
called a child error handler of err_h_parent
and err_h_parent
is
called a parent error handler of err_h_child
.
err_h_dinner <- composerr("Problem with dinner: ")
err_h_dinner_missing <- composerr(
after = " is missing.",
err_h = err_h_dinner
)
err_h_dinner_missing("Wine")
# Error: Problem with dinner: Wine is missing
err_h_dinner_missing("Food")
# Error: Problem with dinner: Food is missing
err_h_dinner("Guests are late.")
# Error: Problem with dinner: Guests are late.
Sometimes, one may even create an error handler from an error handler,
which was itself is a child error handler of another error handler and
so on. Let us assume that err_h10
is a child error handler of err_h9
and err_h9
if a child error handler of err_h8
and so on, down to
err_h1
. If you call err_h10(msg)
, then the resulting error message
is created as follows:
msg_full <- paste0(before_h1, ..., before_h10, msg, after_h10, ..., after_h1)
If we have an error handler err_h1
, then the final error message
processing (also called ultimate error handling) has a large variety
of possibilities:
- throw an error with the created message text (
stop(msg)
) - print a warning with the created message text (
warning(msg)
) - print the created message text as normal text to the console
(
cat(msg)
) - write the created message text to a log file
(
cat(msg, file = FILE, append = TRUE, fill = TRUE)
)
The final error message processing mechanism of an error handler
err_h1
can be defined by setting the ultimate error handler in the
optional argument action
. There are three possibilities how an
ultimate error handler can be assigned to action
:
action
is directly passed toerr_h1("MY ERROR TEXT", action = my_action)
action
is not directly passed toerr_h1()
, but a default ultimate error handler was defined forerr_h1 <- composerr("A problem appeared", action = my_default_action)
- no
action
and no defaultaction
were defined: In this casestop
is used as defaultaction
.
For example, let err_h1
be the following error handler
err_h1 <- composerr("There is a problem: ", action = warning)
err_h1("The fridge is empty.")
# Warning: There is a problem: The fridge is empty.
with a default ultimate error handler that will throw a warning when
err_h1(msg)
is called. In order to alter the behavior of err_h1
to
printing a message instead of throwing a warning, one can call
err_h1("I am a message.", action = message)
# There is a problem: I am a message.
The action
passed to the call err_h1()
will ultimately be used for
the processing of the error message, no matter which default ultimate
error handlers where defined for err_h1
or the ancestor error handlers
of err_h1
. If the action
argument is not passed to call err_h1()
,
then the default ultimate error handler defined for err_h1
(in this
case action = warning
) is used.
Setting the default ultimate error handler when creating an error handler
err_h <- composerr(action = my_default_action)
and optionally overwriting it by another ultimate error handler
my_used_action
when calling the error handler
err_h(msg, action = my_used_action)
allows a very flexible error handling mechanism.
The following example shows the implementation of a vector multiplication function with complete error handling:
my_vec_mult <- function(x, y) {
# create your error handlers
err_h <- composerr("In `my_vec_mult(): `")
err_h_x <- composerr("Invalid argument `x`: ", err_h)
err_h_x <- composerr("Invalid argument `y`: ", err_h)
if (!is.numeric(x))
err_h_x("Not a number.")
if (any(is.na(x)))
err_h_x("Has missings.", action = warning)
if (!is.numeric(y))
err_h_x("Not a number.")
if (any(is.na(y)))
err_h_x("Has missings.", action = warning)
if (length(x) != length(y))
err_h("Vectors `x` and `y` have different length.")
sum(x*y, na.rm = TRUE)
}
my_vec_mult("a", 1:2)
# Error: In `my_vec_mult()`: Invalid argument `x`: Not a number.
my_vec_mult(c(1, NA), 1:2)
# Warning: In `my_vec_mult()`: Invalid argument `x`: Has missings.
# 1
my_vec_mult(1:2, "b")
# Error: In `my_vec_mult()`: Invalid argument `y`: Not a number.
my_vec_mult(1:2, c(1, NA))
# Warning: In `my_vec_mult()`: Invalid argument `y`: Has non-finite values.
# 1
my_vec_mult(1:2, 1:3)
# Error: In `my_vec_mult()`: Vectors `x` and `y` have different length.
my_vec_mult(1:2, 1:2)
# 14
In the next example, we create a function my_sum
, which usually
throws a warning if x
contains missing values. The function also has
an argument suppress_warnings
that allows the warnings to be
suppressed by optionally setting the default ultimate error handler to a
function that does nothing:
my_sum <- function(x, suppress_warnings = FALSE) {
if (isFALSE(suppress_warnings)) {
# set default handler to throwing a warning
err_h <- composerr("Problem in `my_sum()`: ", action = warning)
} else {
# set default handler to doing nothing
err_h <- composerr(action = function(...) {})
}
if (any(is.na(x)))
err_h("`x` has missing values.")
sum(x, na.rm = TRUE)
}
my_sum(c(1, 2, NA))
# 3
# Warning: Problem in `my_sum()`: `x` has missing values.
my_sum(c(1, 2, NA), suppress_warnings = TRUE)
# 3
In the next example, we create an error handler err_h_with_log
from another error handler err_h
, which calls the same ultimate
error handler as for err_h
, but first writes the created error message
to a log file:
logfile <- tempfile()
my_vecmult <- function(x, y) {
err_h <- composerr("Error in call `my_vecmult()`")
if (!is.numeric(x))
err_h("Argument `x` is not numeric.")
# forgot checking `y`
computation_of_vecmult(x, y, err_h)
}
computation_of_vecmult <- function(x, y, err_h) {
log_error <- function(msg)
cat(
paste0(msg, ": Used values: x = ", x, "; y = ", y),
file = logfile,
append = TRUE,
fill = TRUE
)
action_without_log <- composerr_get_action(err_h)
err_h_with_log <- composerr(
err_h = err_h,
action = function(msg, ...) {
log_error(msg)
action_without_log(msg, ...)
}
)
tryCatch(
sum(x*y),
error = function(e) err_h_with_log(e)
)
}
# Caught usage error (not logged)
my_vecmult("a", 1)
# Error: Error in call `my_vecmult(): Argument `x` is not numeric.`
# Caught internal error (logged)
my_vecmult(1, "a")
# Error: Error in call `my_vecmult()`: non-numeric argument for binary operator x * y
cat(paste(readLines(logfile), collapse = "\n"))
# Error in call `my_vecmult()`: non-numeric argument for binary operator x * y Used values: x = 1; y = a
Sometimes it is useful to halt the error processing and just collect the error messages in an internal error stack and flush the entire stack later on. This can be done by calling:
composerr_halt(err_h)
: Halt the error processingerr_h(msg1)
,err_h(msg2)
, …: Not throwing errors any more, but collecting messagesmsg1, msg2, ...
in an internal error stack.composerr_flush(err_h)
: Flush the entire error stack at once
The following example shows a more advanced implementation of
my_vec_mult2
by using a validation routine with a complete error
handling:
validate_numeric_vec <- function(obj, err_h) {
obj_name <- deparse(substitute(obj))
err_h <- composerr(paste0("Invalid argument `", obj_name, "`"), err_h)
if (!is.numeric(obj))
err_h("Not a number.")
err_h_list <- composerr(err_h = composerr("\n", err_h))
composerr_halt(err_h_list)
for (i in seq_along(obj)) {
err_h_item <- composerr(before = paste0(" - Item-", i, " is "), err_h_list)
if (is.na(obj[i]) && !is.nan(obj[i]))
err_h_item("NA.")
if (is.nan(obj[i]))
err_h_item("NaN.")
if (is.infinite(obj[i]))
err_h_item("infinite.")
}
composerr_flush(err_h_list)
invisible(obj)
}
my_vec_mult2 <- function(x, y) {
err_h <- composerr("In `my_vec_mult2()`")
validate_numeric_vec(x, err_h)
validate_numeric_vec(y, err_h)
if (length(x) != length(y))
err_h("Vectors `x` and `y` have different length.")
sum(x*y)
}
my_vec_mult2("a", 1:4)
# Error: In `my_vec_mult2()`: Invalid argument `x`: Not a number.
my_vec_mult2(c(1, NA, NaN, Inf, 5), 1:5)
# Error: In `my_vec_mult2()`: Invalid argument `x`:
# - Item-2 is NA.
# - Item-3 is NaN.
# - Item-4 is infinite.
my_vec_mult2(1:5, c(NaN, 2, 3, NA, Inf))
# Error: In `my_vec_mult2()`: Invalid argument `y`:
# - Item-1 is NA.
# - Item-4 is NaN.
# - Item-5 is infinite.
my_vec_mult2(1:5, 1:4)
# Error: In `my_vec_mult2()`: Vectors `x` and `y` have different length.
my_vec_mult2(1:5, 1:5)
# 55
If you like this package, please give me a star on github: https://github.com/a-maldet/composerr
GPL-3