Question

Is there a way to directly extend/augment/overwrite the original error messages that are thrown when calls to functions that I didn't write myself fail (i.e. functions from base R and contributed packages)?

Example

Consider the following Reference Class:

setRefClass("A", fields=list(x1="character"))

No error when specifying correct values:

new("A", x1="Hello World!")

Resulting error when specifying wrong values:

> new("A", x1=TRUE)
Error: invalid assignment for reference class field 'x1', should be from class "character" or a subclass (was class "logical")

Now, I'd like to include information about the class that actually "caused the problem".

Maybe this could look something like this:

Error: Field assignment error in class 'A':
Invalid assignment for reference class field 'x1', should be from class "character" or a subclass (was class "logical")

My current workarounds for realizing something close often look like this:

setRefClass("A", 
    fields=list(x1="character"),
    methods=list(
        setField=function(field, value) {
            tryCatch(
                .self$field(name=field, value=value),
                warning=function(cond) {
                    message(cond)
                    .self$field(field=field, value=value)    
                },
                error=function(cond) {
                    stop(paste0("Field assignment error in class '", 
                        class(.self), "'\n"), 
                        "ORIGINAL ERROR:\n", as.character(cond)
                    )
                }
            )
        }
    )
)

After instantiating a "blank" object first

x <- new("A")

this would give me the following error when trying to set wrong values to the field via the explicit setter method:

> x$setField(field="x1", value=TRUE)
Error in value[[3L]](cond) : Field assignment error in class 'A'
ORIGINAL ERROR:
Error: invalid assignment for reference class field 'x1', should be from class "character" or a subclass (was class "logical")

I don't like it for the following reasons:

  1. It's a lot of code for just a little extra information and the tryCatch() part makes the code somewhat harder to read.

  2. Due to the nature of tryCatch(), AFAIU I'm obliged to also put the .self$field(field=field, value=value) statement within the warning() function if I expect somewhat "regular" behavior (i.e. "even though there was a warning, the field value is still set"). That's duplicated code.

  3. Seems to me that coercing error messages via as.character() (AFAIU necessary in order to being able to add information via paste() or the like) sometimes cuts the error message off.

  4. That way I'm forced to set my field values via explicit setter functions (<obj>$setField(<field>, <value>)) as opposed to setting them via the "built-in" initialize function (new("A", <field>=<value>))

Was it helpful?

Solution

  1. It turns out to be useful to write a proper constructor that does useful things, including communicating to the user expected argument types, providing appropriate default values, and checking / coercing argument values

    .A <- setRefClass("A", fields=list(x1="character"))
    
    A <- function(x1=character(), ...) {
        if (!is.character(x1))
            stop("field 'x1' should be character()")
        .A(x1=x1, ...)
    }
    
  2. Warnings are more usually handled using withCallingHandler and invoking a restart

    withCallingHandlers({
        warning("oops")
        1
    }, warning=function(warn) {
        warning("I'm handling: ", conditionMessage(warn), call.=FALSE)
        invokeRestart("muffleWarning")
    })
    

    with output

    [1] 1
    Warning message:
    I'm handling: oops 
    

    Use a withCallingHandlers(tryCatch()) paradigm to handle both warnings (with restarts) and errors.

  3. Access the condition message with conditionMessage().
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top