knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE )
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 handler err_h_parent()
.composerr_halt(err_h)
: Halt error processing for the error handler err_h
and accumulate all error messages generated by calling err_h(msg, ...)
in an
internal error stack of err_h
.composerr_flush(err_h)
: Flush entire internal error stack of err_h
,
holding all stacked error messages generated by calling err_h(msg, ...)
.composerr_counterr(err_h)
: Number of accumulated error messages
generated by calling err_h()
.composerr_get_action(err_h)
: Retrieve the ultimate error handler,
which defines the ultimate processing of the full error message, when
err_h(msg, ...)
is called.validate_composerr(err_h)
: Validate that err_h
is indeed an error
handler created by composerr()
.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:
stop(msg)
)warning(msg)
)cat(msg)
)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 to
err_h1("MY ERROR TEXT", action = my_action)
action
is not directly passed to err_h1()
, but a default
ultimate error handler was defined for
err_h1 <- composerr("A problem appeared", action = my_default_action)
action
and no default action
were defined:
In this case stop
is used as default action
.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 messages msg1, msg2, ...
in an internal error stack.composerr_flush(err_h)
: Flush the entire error stack at onceThe 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
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.