Advanced error handling
Asked Answered
G

1

15

I recently posed this question and thankfully was pointed to withRestarts() which seems pretty awesome and powerful to me :-) Now I'm eager to understand R's error handling capabilities in a bit more detail.

Actual questions

  1. What is the recommended usage of simpleCondition()? Never used it before, but I thought it might be useful for designing custom errors and warnings that are in fact "true" conditions. Could it be used to build a database of specific conditions for which specific handlers are available?
  2. Is there a way to "freeze" a certain state of the entire R workspace and return to it to restart a computation at a certain point? I'm aware of save.image(), but AFAIU, this doesn't store the "state" of the search path (search() or searchpaths()).

For those interested

Two code examples

  1. illustration of my current use of withRestarts in dependence on this blog post
  2. attempt to define a "custom condition"

I'd appreciate any comments/suggestion on what to do better ;-)

Example 1

require("forecast")
autoArimaFailsafe <- function(
    x,
    warning=function(w, ...) {
        message("autoArimaFailsafe> warning:")
        message(w)
        invokeRestart("donothing")},
    error=function(e, ...) {
        message("autoArimaFailsafe> error:")
        message(e)
        invokeRestart("abort")}
) {
    withRestarts(
        out <- tryCatch(
           {
                expr <- expression(auto.arima(x=x))
                return(eval(expr))
           },
           warning=warning,
           error=error 
        ),
        donothing=function(...) {
            return(eval(expr))
        },
        abort=function(...) {
            message("aborting")
            return(NULL)
        }
    )    
}
data(AirPassengers)
autoArimaFailsafe(x=AirPassengers)
autoArimaFailsafe(x="a")

Example 2

require("forecast")
autoArimaFailsafe <- function(
    x,
    warning=function(w, ...) {
        message("autoArimaFailsafe> warning")
        invokeRestart("donothing")},
    error=function(e, ...) {
        message("autoArimaFailsafe> error")
        invokeRestart("abort")},
    condition=function(cond, ...) {
        out <- NULL
        message(cond)
        condmsg     <- conditionMessage(c=cond)
        condclass   <- class(cond)
        if (any(class(cond) == "simpleWarning")) {
            out <- warning(w=cond)
        } else if (any(class(cond) == "simpleError")) {
            out <- error(e=cond)
        } else if (any(class(cond) == "simpleCondition")) {
            if (condmsg == "invalid class: character") {
                out <- invokeRestart("forcedefault")
            }
        }
        return(out)
    }
) {
    withRestarts(
        out <- tryCatch(
           {
                expr <- expression(auto.arima(x=x))
                if (class(x) == "character") {
                    expr <- signalCondition(
                        simpleCondition("invalid class: character", 
                            call=as.call(expr))
                    )
                }
                return(eval(expr))
           },
           condition=condition
        ),
        donothing=function(...) {return(eval(expr))},
        abort=function(...) {
            message("aborting")
            return(NULL)
        },
        forcedefault=function(...) {
            data(AirPassengers)
            expr <- expression(auto.arima(x=AirPassengers))
            return(eval(expr))
        } 
    )
}
autoArimaFailsafe(x=AirPassengers)
autoArimaFailsafe(x=NULL)
autoArimaFailsafe(x="a")
Greece answered 12/3, 2013 at 13:47 Comment(0)
S
15

This post references the inspiration for R's condition handling.

For 1., I think of simpleCondition as illustrating how one can construct custom conditions, e.g,.

 myCondition <-
    function(message, call=NULL, type=c("overflow", "underflow", "zero"))
{
    type <- match.arg(type)             # only allowed types past here
    class <- c(type, "my", "condition")
    structure(list(message = as.character(message), call = call), 
        class = class)
}

is a constructor for making custom conditions

> myCondition("oops")
<overflow: oops>
> myCondition("oops", type="underflow")
<underflow: oops>

These conditions can be used in tryCatch or withCallingHandlers

xx <- tryCatch({
    signalCondition(myCondition("oops", type="underflow"))
}, underflow=function(e) {
    message("underflow: ", conditionMessage(e))
    NA # return value, assigned to xx
})

These are S3 classes so can have a linear hierarchy -- bad and worse are both subclasses of error.

myError <-
    function(message, call=NULL, type=c("bad", "worse"))
{
    type <- match.arg(type)
    class <- c(type, "error", "condition")
    structure(list(message=as.character(message), call=call),
              class=class)
}

One might also create an error that extends the 'simpleError' S3 class as cond <- simpleError("oops"); class(cond) = c("myerr", class(cond)

With tryCatch we just get access to a single handler, the first (in the sense described on ?tryCatch) to match the class of condition

tryCatch({
    stop(myError("oops", type="worse"))
}, bad = function(e) {
    message("bad error: ", conditionMessage(e))
}, worse = function(e) {
    message("worse error: ", conditionMessage(e))  # here's where we end up
}, error=function(e) {
    message("error: ", conditionMessage(e))
})

With withCallingHandlers we have the opportunity to hit multiple handlers, provided we don't invoke a restart

withCallingHandlers({
    stop(myError("oops", type="bad"))
}, bad = function(e) {                             # here...
    message("bad error: ", conditionMessage(e))
}, worse = function(e) {
    message("worse error: ", conditionMessage(e))
}, error=function(e) {                             # ...and here...
    message("error: ", conditionMessage(e))
})                                                 # ...and top-level 'error'

withCallingHandlers({
    x <- 1
    warning(myError("oops", type="bad"))
    "OK"
}, bad = function(e) {                     # here, but continue at the restart
    message("bad warning: ", conditionMessage(e))
    invokeRestart("muffleWarning")
}, worse = function(e) {
    message("worse warning: ", conditionMessage(e))
})

I'm not so sure about your question 2; I think this is the situation that calling handlers are designed to address -- the entire frame where the condition was invoked is poised waiting to continue, once you invoke the restart.

Schuman answered 15/3, 2013 at 21:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.